# Check requisite packages are installed.
packages <- c(
  "plotly", 
  "dplyr",
  "ReacTran",
  "deSolve"
)
for (pkg in packages) {
  library(pkg, character.only = TRUE)
}
package 㤼㸱plotly㤼㸲 was built under R version 4.0.5Loading required package: ggplot2
package 㤼㸱ggplot2㤼㸲 was built under R version 4.0.3
Attaching package: 㤼㸱plotly㤼㸲

The following object is masked from 㤼㸱package:ggplot2㤼㸲:

    last_plot

The following object is masked from 㤼㸱package:stats㤼㸲:

    filter

The following object is masked from 㤼㸱package:graphics㤼㸲:

    layout

package 㤼㸱dplyr㤼㸲 was built under R version 4.0.4
Attaching package: 㤼㸱dplyr㤼㸲

The following objects are masked from 㤼㸱package:stats㤼㸲:

    filter, lag

The following objects are masked from 㤼㸱package:base㤼㸲:

    intersect, setdiff, setequal, union

package 㤼㸱ReacTran㤼㸲 was built under R version 4.0.5Loading required package: rootSolve
package 㤼㸱rootSolve㤼㸲 was built under R version 4.0.3Loading required package: deSolve
package 㤼㸱deSolve㤼㸲 was built under R version 4.0.3Loading required package: shape
package 㤼㸱shape㤼㸲 was built under R version 4.0.5

Load

Pulling code almost directly from LM1996-NumPoolComScaling-Results-2021-05.Rmd.

dirViking <- c(
  file.path(
    getwd(), "LCAB_LawMorton1996-NumericalPoolCommunityScaling"
  ),
  file.path(
    getwd(), "LCAB_LawMorton1996-NumericalPoolCommunityScaling2"
  )
)
dirVikingResults <- file.path(
  dirViking, c("results-2021-04", "save-2021-05-10") # Latter not 100% yet.
)
resultFormat <- paste0(
  "run-", 
  "%d", # Combination Number, or CombnNum.
  "-", 
  "%s", # Run Seed.
  ".RDS"
)

2021-04 Data

# Copied from LawMorton1996-NumericalPoolCommunityScaling-Calculation.R
set.seed(38427042)

basal <- c(3, 10, 30, 100, 300, 1000)
consumer <- c(3, 10, 30, 100, 300, 1000) * 2
events <- (max(basal) + max(consumer)) * 2
runs <- 100

logBodySize <- c(-2, -1, -1, 1) # Morton and Law 1997 version.
parameters <- c(0.01, 10, 0.5, 0.2, 100, 0.1)

# Need to rerun seedsPrep to get the random number generation right for seedsRun
seedsPrep <- runif(2 * length(basal) * length(consumer)) * 1E8
seedsRun <- runif(runs * length(basal) * length(consumer)) * 1E8
paramFrame <- with(list(
  b = rep(basal, times = length(consumer)),
  c = rep(consumer, each = length(basal)),
  s1 = seedsPrep[1:(length(basal) * length(consumer))],
  s2 = seedsPrep[
    (length(basal) * length(consumer) + 1):(
      2 * length(basal) * length(consumer))
  ],
  sR = seedsRun
), {
  temp <- data.frame(
    CombnNum = 0,
    Basals = b,
    Consumers = c,
    SeedPool = s1,
    SeedMat = s2,
    SeedRuns = "",
    SeedRunsNum = 0,
    EndStates = I(rep(list(""), length(b))),
    EndStatesNum = 0,
    EndStateSizes = I(rep(list(""), length(b))),
    EndStateSizesNum = NA,
    EndStateAssembly = I(rep(list(""), length(b))),
    EndStateAbundance = I(rep(list(""), length(b))),
    Dataset = "2021-04",
    DatasetID = 1,
    stringsAsFactors = FALSE
  )
  for (i in 1:nrow(temp)) {
    seeds <- sR[((i - 1) * runs + 1) : (i * runs)]
    temp$SeedRuns[i] <- toString(seeds) # CSV
    temp$SeedRunsNum[i] <- length(seeds)
  }
  temp$CombnNum <- 1:nrow(temp)
  temp
})
# Note: n + 2 end states. Failure to finish, failure to obtain state, and state.
for (i in 1:nrow(paramFrame)) {
  resultsList <- list(
    "No Run" = 0,
    "No State" = 0
  )
  resultsSize <- list(
    "0" = 0
  )
  resultsAssembly <- list(
    "No Run" = data.frame(),
    "No State" = data.frame()
  )
  seeds <- unlist(strsplit(paramFrame$SeedRuns[i], ', '))
  for (seed in seeds) {
    fileName <- file.path(
      dirVikingResults[paramFrame$DatasetID[i]],
      sprintf(resultFormat, paramFrame$CombnNum[i], seed)
    )
    
    if (file.exists(fileName)) {
      temp <- load(fileName)
      temp <- eval(parse(text = temp)) # Get objects.
      
      if (is.data.frame(temp)) {
        community <- toString(
          temp[[ncol(temp)]][[nrow(temp)]]
        )
        size <- toString(length(temp[[ncol(temp)]][[nrow(temp)]]))
        
        if (community == "") {
          resultsList$`No State` <- resultsList$`No State` + 1
          resultsSize$`0` <- resultsSize$`0` + 1
          
        } else if (community %in% names(resultsList)) {
          resultsList[[community]] <- resultsList[[community]] + 1
          resultsSize[[size]] <- resultsSize[[size]] + 1
          
        } else {
          resultsList[[community]] <- 1
          resultsAssembly[[community]] <- temp
          
          if (size %in% resultsSize) {
            resultsSize[[size]] <- resultsSize[[size]] + 1
          } else {
            resultsSize[[size]] <- 1
          }
        }
      } else {
        resultsList$`No State` <- resultsList$`No State` + 1
        resultsSize$`0` <- resultsSize$`0` + 1
      }
    } else {
      resultsList$`No Run` <- resultsList$`No Run` + 1
      resultsSize$`0` <- resultsSize$`0` + 1
    }
  }
  
  paramFrame$EndStates[[i]] <- resultsList
  paramFrame$EndStatesNum[i] <- length(resultsList) - 2 # ! No State, No Run
  paramFrame$EndStateSizes[[i]] <- resultsSize
  paramFrame$EndStateSizesNum[i] <- length(resultsSize) - 1 # ! 0
  paramFrame$EndStateAssembly[[i]] <- resultsAssembly
}

2021-05 Data

source(
  file.path(getwd(), 
            "LawMorton1996-NumericalPoolCommunityScaling-Settings2.R")
)

oldNrow <- nrow(paramFrame)

paramFrame <- rbind(paramFrame, with(list(
  b = rep(basal, times = length(consumer)),
  c = rep(consumer, each = length(basal)),
  s1 = seedsPrep[1:(length(basal) * length(consumer))],
  s2 = seedsPrep[
    (length(basal) * length(consumer) + 1):(
      2 * length(basal) * length(consumer))
  ],
  sR = seedsRun
), {
  temp <- data.frame(
    CombnNum = 0,
    Basals = b,
    Consumers = c,
    SeedPool = s1,
    SeedMat = s2,
    SeedRuns = "",
    SeedRunsNum = 0,
    EndStates = I(rep(list(""), length(b))),
    EndStatesNum = 0,
    EndStateSizes = I(rep(list(""), length(b))),
    EndStateSizesNum = NA,
    EndStateAssembly = I(rep(list(""), length(b))),
    EndStateAbundance = I(rep(list(""), length(b))),
    Dataset = "2021-05",
    DatasetID = 2,
    stringsAsFactors = FALSE
  )
  for (i in 1:nrow(temp)) {
    seeds <- sR[((i - 1) * runs + 1) : (i * runs)]
    temp$SeedRuns[i] <- toString(seeds) # CSV
    temp$SeedRunsNum[i] <- length(seeds)
  }
  temp$CombnNum <- 1:nrow(temp)
  temp
})
)
# Note: n + 2 end states. Failure to finish, failure to obtain state, and state.
# Modified from above, but with the abundance recorded.
for (i in (oldNrow + 1):nrow(paramFrame)) {
  resultsList <- list(
    "No Run" = 0,
    "No State" = 0
  )
  resultsSize <- list(
    "0" = 0
  )
  resultsAssembly <- list(
    "No Run" = data.frame(),
    "No State" = data.frame()
  )
  resultsAbund <- list(
    "No Run" = "",
    "No State" = ""
  )
  seeds <- unlist(strsplit(paramFrame$SeedRuns[i], ', '))
  for (seed in seeds) {
    fileName <- file.path(
      dirVikingResults[paramFrame$DatasetID[i]],
      sprintf(resultFormat, paramFrame$CombnNum[i], seed)
    )
    
    if (file.exists(fileName)) {
      temp <- load(fileName)
      temp <- eval(parse(text = temp)) # Get objects.
      
      if (is.list(temp) && "Result" %in% names(temp)) {
        
        if (is.data.frame(temp$Result))
          community <- temp$Result$Community[[nrow(temp$Result)]]
        else 
          community <- temp$Result
        
        size <- toString(length(community))
        
        if (community[1] != "") 
          abund <- toString(temp$Abund[community + 1])
        else 
          abund <- ""
        
        community <- toString(community)
        
        if (community == "") {
          resultsList$`No State` <- resultsList$`No State` + 1
          resultsSize$`0` <- resultsSize$`0` + 1
          
        } else if (community %in% names(resultsList)) {
          resultsList[[community]] <- resultsList[[community]] + 1
          resultsSize[[size]] <- resultsSize[[size]] + 1
          
        } else {
          resultsList[[community]] <- 1
          resultsAssembly[[community]] <- temp
          resultsAbund[[community]] <- abund
          
          if (size %in% resultsSize) {
            resultsSize[[size]] <- resultsSize[[size]] + 1
          } else {
            resultsSize[[size]] <- 1
          }
        }
      } else {
        resultsList$`No State` <- resultsList$`No State` + 1
        resultsSize$`0` <- resultsSize$`0` + 1
      }
    } else {
      resultsList$`No Run` <- resultsList$`No Run` + 1
      resultsSize$`0` <- resultsSize$`0` + 1
    }
  }
  
  paramFrame$EndStates[[i]] <- resultsList
  paramFrame$EndStatesNum[i] <- length(resultsList) - 2 # ! No State, No Run
  paramFrame$EndStateSizes[[i]] <- resultsSize
  paramFrame$EndStateSizesNum[i] <- length(resultsSize) - 1 # ! 0
  paramFrame$EndStateAssembly[[i]] <- resultsAssembly
  paramFrame$EndStateAbundance[[i]] <- resultsAbund
}

Test Data

testRowNums <- nrow(paramFrame)
testRowsToAdd <- c(2, 6) # Make sure to put in numerical order!

paramFrame <- with(
  list(
    basal2 = c(5, 10, 15),
    consumer2 = c(20, 40, 60),
    logBodySize = c(-2, -1, -1, 0),
    parameters = c(0.01, 10, 0.5, 0.2, 100, 0.1)
  ),
  {
    set.seed(3680180)
    seedsPrep2 <- runif(2 * length(basal2) * length(consumer2)) * 1E8
    with(list(
      b = rep(basal2, times = length(consumer2)),
      c = rep(consumer2, each = length(basal2)),
      s1 = seedsPrep2[1:(length(basal2) * length(consumer2))],
      s2 = seedsPrep2[
        (length(basal2) * length(consumer2) + 1):(
          2 * length(basal2) * length(consumer2))
      ]
    ), {
      rbind(
        paramFrame,
        data.frame(
          CombnNum = testRowsToAdd,
          Basals = b[testRowsToAdd],
          Consumers = c[testRowsToAdd],
          SeedPool = s1[testRowsToAdd],
          SeedMat = s2[testRowsToAdd],
          SeedRuns = "",
          SeedRunsNum = 0,
          EndStates = I(rep(list(""), length(testRowsToAdd))),
          EndStatesNum = 0,
          EndStateSizes = I(rep(list(""), length(testRowsToAdd))),
          EndStateSizesNum = NA,
          EndStateAssembly = I(rep(list(""), length(testRowsToAdd))),
          EndStateAbundance = I(rep(list(""), length(testRowsToAdd))),
          Dataset = "Test",
          DatasetID = max(paramFrame$DatasetID) + 1,
          stringsAsFactors = FALSE
        )
      )
    }
    )
  }
)

testRowNums <- (testRowNums + 1):nrow(paramFrame)
resultsList <- list(
  list(
    "No Run" = 0,
    "No State" = 0,
    "2, 4, 6, 12, 29" = 1,
    "2, 4, 6, 13, 29" = 1
  ),
  list(
    "No Run" = 0,
    "No State" = 0,
    "8, 10, 12, 14, 15, 16, 39, 43" = 1,
    "8, 12, 14, 15, 16, 38, 39" = 1
  )
)
resultsSize <- list(
  list(
    "0" = 0,
    "5" = 2
  ),
  list(
    "0" = 0,
    "8" = 1,
    "7" = 1
  )
)
resultsAbund <- list(
  list(
    "No Run" = "",
    "No State" = "",
    "2, 4, 6, 12, 29" = "742.88553671712, 80.579233072626, 162.128399850253, 20.2082198699389, 18.8589490510429",
    "2, 4, 6, 13, 29" = "668.664143581837, 119.024146851052, 127.680269383867, 30.657960866033, 13.4844194707944"
  ),
  list(
    "No Run" = "",
    "No State" = "",
    "8, 10, 12, 14, 15, 16, 39, 43" = "20.7665807606491, 32.4461165261454, 80.4033387818895, 817.879722033354, 121.136570782828, 18.0390671088957, 12.3834561177271, 19.9674718543196",
    "8, 12, 14, 15, 16, 38, 39" = "82.592048492812, 138.267379166014, 938.158436379166, 51.8610963745021, 5.03556251837491, 14.1019343145825, 25.9231062711228"
  )
)

for (j in seq_along(testRowNums)) {
  i <- testRowNums[j]
  paramFrame$EndStates[[i]] <- resultsList[[j]]
  paramFrame$EndStatesNum[i] <- length(resultsList[[j]]) - 2 # ! No State, No Run
  paramFrame$EndStateSizes[[i]] <- resultsSize[[j]]
  paramFrame$EndStateSizesNum[i] <- length(resultsSize[[j]]) - 1 # ! 0
  paramFrame$EndStateAbundance[[i]] <- resultsAbund[[j]]
}

Plot

# X, Y, Basal and Consumer.
# Z = Sizes of the Endstates.

plotScalingData <- data.frame(
  CombnNum = rep(paramFrame$CombnNum, paramFrame$EndStatesNum),
  Basals = rep(paramFrame$Basals, paramFrame$EndStatesNum),
  Consumers = rep(paramFrame$Consumers, paramFrame$EndStatesNum),
  Dataset = rep(paramFrame$Dataset, paramFrame$EndStatesNum),
  DatasetID = rep(paramFrame$DatasetID, paramFrame$EndStatesNum)
)

# Communities
comms <- unlist(lapply(paramFrame$EndStates, names))
freqs <- unlist(paramFrame$EndStates)
asmbl <- unlist(paramFrame$EndStateAssembly, recursive = FALSE)
asmbl <- asmbl[comms != "No Run" & comms != "No State"]
freqs <- freqs[comms != "No Run" & comms != "No State"]
comms <- comms[comms != "No Run" & comms != "No State"]

asmbl <- lapply(asmbl, function(d) {
  if (is.null(d)) return(NA)
  if ("Result.Outcome" %in% names(d))
    d %>% dplyr::filter(Result.Outcome != "Type 1 (Failure)" & 
                          Result.Outcome != "Present")
  else
    d$Result %>% dplyr::filter(Outcome != "Type 1 (Failure)" & 
                                 Outcome != "Present")
})

plotScalingData$Communities <- comms
plotScalingData$CommunityFreq <- freqs
plotScalingData$CommunitySeq <- asmbl

# Community Size
temp <- unlist(lapply(strsplit(plotScalingData$Communities, ','), length))
plotScalingData$CommunitySize <- temp

# For usage by the reader.

plotScaling <- plotly::plot_ly(
  plotScalingData,
  x = ~Basals,
  y = ~Consumers,
  z = ~CommunitySize,
  color = ~Dataset,
  colors = c("red", "blue", "black")
)

plotScaling <- plotly::add_markers(plotScaling)

plotScaling <- plotly::layout(
  plotScaling,
  scene = list(
    xaxis = list(type = "log"),
    yaxis = list(type = "log"),
    camera = list(
      eye = list(
        x = -1.25, y = -1.25, z = .05
      )
    )
  )
)

plotScaling

Abundances

# > runif(1) * 1E8
# [1] 82598679
set.seed(82598679)

mats <- list()
poolsall <- list() # name pools used in save data; be careful!

for (i in 1:length(dirViking)) {
  temp <- load(file.path(
    dirViking[i], 
    paste0("LawMorton1996-NumericalPoolCommunityScaling-PoolMats", 
           if (i > 1) i else "", 
           ".RDS")
  ))
  mats[[i]] <- eval(parse(text = temp[1]))
  poolsall[[i]] <- eval(parse(text = temp[2]))
}
pools <- poolsall

# Add in the test datasets.
poolsTemp <- list()
matsTemp <- list()
for (r in testRowNums) {
  testRowRow <- paramFrame[r, ]
  poolsTemp[[testRowRow$CombnNum]] <- with(testRowRow,
    RMTRCode2::LawMorton1996_species(
      Basal = Basals,
      Consumer = Consumers,
      Parameters = c(0.01, 10, 0.5, 0.2, 100, 0.1),
      LogBodySize = c(-2, -1, -1, 0),
      seed = SeedPool
    )
  )
  matsTemp[[testRowRow$CombnNum]] <- with(testRowRow,
    RMTRCode2::LawMorton1996_CommunityMat(
      Pool = poolsTemp[[CombnNum]],
      Parameters = c(0.01, 10, 0.5, 0.2, 100, 0.1),
      seed = SeedMat
    )
  )
}
executing %dopar% sequentially: no parallel backend registered
pools[[i + 1]] <- poolsTemp
mats[[i + 1]] <- matsTemp

oldCandidateData <- load(file.path(getwd(), "candidateDataSoFar.Rdata"))
oldCandidateData <- eval(parse(text = oldCandidateData))
candidateData <- plotScalingData %>% dplyr::group_by(
  CombnNum, Dataset
) %>% dplyr::mutate(
  OtherSteadyStates = dplyr::n() - 1
) %>% dplyr::filter(
  OtherSteadyStates > 0
)
candidateData %>% dplyr::select(-CommunitySeq)
# First, check if it is in the paramFrame.
# Second, check if it is in the saved data from the previous.
# Otherwise, ignore it, we'll figure out what it is and why it is missing later.

candidateData$CommunityAbund <- ""

for (r in 1:nrow(candidateData)) {
  # ID 1:4 are used to identify paramFrame, 5 used to identify abundance
  ID <- candidateData[r, 1:6]
  paramFrameRow <- paramFrame %>% dplyr::filter(
    CombnNum == ID$CombnNum,
    Basals == ID$Basals,
    Consumers == ID$Consumers,
    Dataset == ID$Dataset
  )
  
  if (is.list(paramFrameRow$EndStateAbundance[[1]])) {
    entry <- which(ID$Communities == names(paramFrameRow$EndStateAbundance[[1]]))
    if (length(entry)) {
      candidateData$CommunityAbund[r] <- paramFrameRow$EndStateAbundance[[1]][[entry]]
      next()
    }
  }
  
  if (ID$Dataset == "2021-04") {
    
    oldCandDatRow <- oldCandidateData %>% dplyr::filter(
      CombnNum == ID$CombnNum,
      Basals == ID$Basals,
      Consumers == ID$Consumers,
      Communities == ID$Communities
    )
    
    if (nrow(oldCandDatRow) > 0) {
      if (oldCandDatRow$CommunityAbund != "") {
        candidateData$CommunityAbund[r] <- oldCandDatRow$CommunityAbund
      }
    }
  }
}
for (r in 1:nrow(candidateData)) {
  if (!(candidateData$CommunityAbund[r] == "Failure" |
      candidateData$CommunityAbund[r] == "")) next

  # Random guesses, starting from structured.
  temp <- with(
    candidateData[r, ],
    RMTRCode2::FindSteadyStateFromEstimate(
      Pool = pools[[DatasetID]][[CombnNum]],
      InteractionMatrix = mats[[DatasetID]][[CombnNum]],
      Community = Communities,
      Populations = ifelse(
        pools[[DatasetID]][[CombnNum]]$Type[
          RMTRCode2::CsvRowSplit(Communities)
        ] == "Basal",
        1000, 10)
    )
  )

  if (any(temp) < 1E-4) {
    temp <- "EstimateFailure"
  } else {
    temp <- toString(temp)
  }
  candidateData$CommunityAbund[r] <- temp
}
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 2843.06
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 2876.33
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 2870.65
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 2873.23
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 2871.69
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they gocoercing argument of type 'double' to logicalcoercing argument of type 'double' to logicalcoercing argument of type 'double' to logicalcoercing argument of type 'double' to logicalcoercing argument of type 'double' to logicalcoercing argument of type 'double' to logical
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 6597.38
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they gocoercing argument of type 'double' to logical
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 3592.76
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 3780.53
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 3786.89
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 3784.07
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 3778.43
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 3777.75
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 3774.98
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 3781.84
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they gocoercing argument of type 'double' to logical
candidateData <- candidateData %>% dplyr::filter(CommunityAbund != "",
                                                 CommunityAbund != "Failure",
                                                 CommunityAbund != "EstimateFailure")
candidateData$CommunityProd <- NA
for (r in 1:nrow(candidateData)) {
  candidateData$CommunityProd[r] <- with(
    candidateData[r, ], 
    RMTRCode2::Productivity(
      Pool = pools[[DatasetID]][[CombnNum]], 
      InteractionMatrix = mats[[DatasetID]][[CombnNum]], 
      Community = Communities, 
      Populations = CommunityAbund
    )
  )
}

Simple Pipe Results

functionCommunity <- function(
    time, 
    Concentration, 
    parameters, 
    GridThickness,# = Grid,
    Pool,# = preprocessed$redDisPool, 
    Mat,# = preprocessed$redComMat, 
    Area = 1, 
    Diffusion = 1, 
    Verbose = FALSE,
    ExtinctionThreshold = 0, 
    Circular = FALSE
) {
    if(Verbose) print(Concentration)
    
    Concentration[Concentration < ExtinctionThreshold] <- 0
    
    # Columns are per species pipes,
    # Rows are the same point on a pipe but for each species.
    pipeMatrix <- matrix(data = Concentration, ncol = nrow(Pool))
    # Note that matrix automatically fills downward!
    
    # Idea with circular: attach duplicates of first, last evaluation points.
    # Then evaluate as normal. Before finishing, remove the duplicate entries.
    # Order of additions matters.
    if (Circular > 0) {
      pipeMatrix <- rbind(pipeMatrix[-Circular:-1 + 1 +nrow(pipeMatrix), ],
                          pipeMatrix,
                          pipeMatrix[1:Circular, ])
      
      if ("dx" %in% names(GridThickness)) {
        GridThickness <- GridThickness$dx
      }
      GridThickness <- c(GridThickness[-Circular:-1 + 1 + length(GridThickness)],
                         GridThickness,
                         GridThickness[1:Circular])
    }
    
    if(Verbose) print(pipeMatrix)
    
    # Diffusion
    pipeMatrixTran <- apply(pipeMatrix, MARGIN = 2, FUN = function(C, A, D, G) {
      ReacTran::tran.1D(
        C = C, A = A, D = D, dx = G
      )$dC
    }, A = Area, D = Diffusion, G = GridThickness)
    
    if(Verbose) print(pipeMatrixTran)
    
    # Point-wise community dynamics
    pipeMatrixComm <- t(apply(
      pipeMatrix, MARGIN = 1, FUN = function(C, t, P, M) {
      unlist(RMTRCode2::GeneralisedLotkaVolterra(t = t, y = C, 
                                      parms = list(
                                        a = M,
                                        r = P$ReproductionRate
                                      )))
    }, t = time, P = Pool, M = Mat))
    
    if(Verbose) print(pipeMatrixComm)
    
    # Order of deletions does not matter.
    if(Circular) pipeMatrixTran <- pipeMatrixTran[
      c(-1:-Circular, -1:-Circular + 1 + nrow(pipeMatrixTran)), ] 
    if(Circular) pipeMatrixComm <- pipeMatrixComm[
      c(-1:-Circular, -1:-Circular + 1 + nrow(pipeMatrixComm)), ] 
    
    list(pipeMatrixTran + pipeMatrixComm)
  }

pipeFUN <- function(i, dat, pool, mat, 
                    GridL = 1, GridCells = 100,
                    Time = seq(0, 5000, by = 1)) {
  temp <- dat[i, ]
  
  preprocessed <- RMTRCode2::IslandPreprocess(
    Pool = pool, InteractionMatrix = mat,
    Communities = c(
      list(temp$Communities[1]),
      list(temp$Communities[2])
    ),
    Populations = c(
      list(temp$CommunityAbund[1]),
      list(temp$CommunityAbund[2])
    ),
    DispersalPool = 0,
    DispersalIsland = matrix(0, nrow = 2, ncol = 2),
    Tolerance = 0, 
    Verbose = FALSE
  )
  
  # Setup the de system.
  Grid <- ReacTran::setup.grid.1D(L = GridL, N = GridCells)
  
  initialCondition <- with(preprocessed, rbind(
    # Community 1
    matrix(abundance_init[1:length(redCom)], ncol = length(redCom)),
    # Space
    matrix(0, nrow = GridCells - 2, ncol = length(redCom)),
    # Community 2
    matrix(abundance_init[length(redCom) + 1:length(redCom)], ncol = length(redCom))
  ))
  
  deSolve::ode.1D(
    y = initialCondition, 
    times = Time,
    func = functionCommunity,
    parms = preprocessed$parameters,
    D = 0.001,
    nspec = length(preprocessed$redCom),
    Verbose = FALSE,
    GridThickness = Grid,
    Pool = preprocessed$redPool, 
    Mat = preprocessed$redComMat, 
  )
}
# For each group-dataset,
# For each pair,
# Run Pipe Dynamics,
# Save the result with its pairing
candidateData$TotalID <- paste(candidateData$CombnNum, candidateData$DatasetID)

pipeInteractions <- list()
pipeInteractionsFull <- list()

for (grp in unique(candidateData$TotalID)) {
  candidateDataSubset <- candidateData %>% dplyr::filter(TotalID == grp)
  
  if (nrow(candidateDataSubset) == 1) next()
  
  pairingResults <- combn(
    nrow(candidateDataSubset), 2, 
    pipeFUN,
    dat = candidateDataSubset, 
    pool = pools[[
      candidateDataSubset$DatasetID[1]
    ]][[candidateDataSubset$CombnNum[1]]],
    mat = mats[[
      candidateDataSubset$DatasetID[1]
    ]][[candidateDataSubset$CombnNum[1]]],
    simplify = FALSE
  )
  
  pairingResultsAbunds <- lapply(
    pairingResults, function(mat, cells = 100) {
      # Final outcomes, without time.
      mat <- mat[nrow(mat), -1]
      retVal <- list()
      species <- length(mat) / cells
      # The results are copies of the pipe by species
      # We want the end points.
      for (i in c(1, cells)) {
        retVal[[toString(i)]] <- mat[i + cells * ((1:species) - 1)]
      }
      retVal
    }
  )
  
  pipeInteractionsFull[[grp]] <- pairingResults
  pipeInteractions[[grp]] <- pairingResultsAbunds
}
# Format of table should be:
# ID, Community 1, Community 2, Outcomes 1-2, Outcomes 1-0-2
# For outcomes, species presence will be used.

communities <- NULL
totalCommunities <- NULL
for (grp in unique(candidateData$TotalID)) {
  candidateDataSubset <- candidateData %>% dplyr::filter(TotalID == grp)
  
  if (nrow(candidateDataSubset) > 1) {
    newCommunities <- combn(
      candidateDataSubset$Communities, 2, 
    )
    communities <- c(communities, newCommunities)
    totalCommunities <- c( # Labelling is wrong here. Need tC from pairs of nC.
      totalCommunities,
      list(apply(newCommunities, 2, function(coms) {
        toString(sort(unique(RMTRCode2::CsvRowSplit(coms))))
      }))
    )
  }
}

minThresh <- lapply(candidateData$CommunityAbund, function(x) min(RMTRCode2::CsvRowSplit(x)) * 0.1) %>% unlist %>% min

pipeInteractionsWhich <- unlist(lapply(
  seq_along(pipeInteractions), function(i, byID, communities) {
    lapply(byID[[i]], function(system, comms) {
      lapply(system, function(pipeEnd, comms) {
        toString(RMTRCode2::CsvRowSplit(comms)[
          which(pipeEnd > minThresh)
        ])
      }, comms = comms)
    }, comms = communities[[i]])
  }, byID = pipeInteractions, communities = totalCommunities
))

pipeInteractionResults <- data.frame(
  DatasetID = rep(names(pipeInteractions), 
                  unlist(lapply(pipeInteractions, length))),
  Community1 = communities[seq(from = 1, to = length(communities), by = 2)],
  Community2 = communities[seq(from = 2, to = length(communities), by = 2)],
  Outcome_Pipe1 = pipeInteractionsWhich[
    seq(from = 1, to = length(pipeInteractionsWhich), by = 2)],
  Outcome_Pipe2 = pipeInteractionsWhich[
    seq(from = 1, to = length(pipeInteractionsWhich), by = 2)]
)

pipeInteractionResults
pipeInteractionResults %>% dplyr::mutate(
  C1Invaded = Community1 != Outcome_Pipe1,
  C2Invaded = Community2 != Outcome_Pipe2,
  Stalemate = !C1Invaded & !C2Invaded,
  Hybrid = C1Invaded & C2Invaded,
) %>% dplyr::select(-dplyr::starts_with("Outcome"))

Save workspace

save(
  candidateData,
  pipeInteractions,
  pipeInteractionsWhich,
  mats,
  paramFrame,
  plotScalingData,
  pools,
  file = "LM1996-NumPoolCom-QDatDif-2021-07.RData"
)
LS0tDQp0aXRsZTogIkFuc3dlcmluZyBRdWVzdGlvbnM6IEdhdGhlciBEYXRhOiBEaWZmdXNpb24sIDIwMjEtMDciDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQotLS0NCg0KYGBge3IgbGlic30NCiMgQ2hlY2sgcmVxdWlzaXRlIHBhY2thZ2VzIGFyZSBpbnN0YWxsZWQuDQpwYWNrYWdlcyA8LSBjKA0KICAicGxvdGx5IiwgDQogICJkcGx5ciIsDQogICJSZWFjVHJhbiIsDQogICJkZVNvbHZlIg0KKQ0KZm9yIChwa2cgaW4gcGFja2FnZXMpIHsNCiAgbGlicmFyeShwa2csIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkNCn0NCmBgYA0KDQojIExvYWQNClB1bGxpbmcgY29kZSBhbG1vc3QgZGlyZWN0bHkgZnJvbSBgTE0xOTk2LU51bVBvb2xDb21TY2FsaW5nLVJlc3VsdHMtMjAyMS0wNS5SbWRgLg0KYGBge3IgZGlyc30NCmRpclZpa2luZyA8LSBjKA0KICBmaWxlLnBhdGgoDQogICAgZ2V0d2QoKSwgIkxDQUJfTGF3TW9ydG9uMTk5Ni1OdW1lcmljYWxQb29sQ29tbXVuaXR5U2NhbGluZyINCiAgKSwNCiAgZmlsZS5wYXRoKA0KICAgIGdldHdkKCksICJMQ0FCX0xhd01vcnRvbjE5OTYtTnVtZXJpY2FsUG9vbENvbW11bml0eVNjYWxpbmcyIg0KICApDQopDQpkaXJWaWtpbmdSZXN1bHRzIDwtIGZpbGUucGF0aCgNCiAgZGlyVmlraW5nLCBjKCJyZXN1bHRzLTIwMjEtMDQiLCAic2F2ZS0yMDIxLTA1LTEwIikgIyBMYXR0ZXIgbm90IDEwMCUgeWV0Lg0KKQ0KcmVzdWx0Rm9ybWF0IDwtIHBhc3RlMCgNCiAgInJ1bi0iLCANCiAgIiVkIiwgIyBDb21iaW5hdGlvbiBOdW1iZXIsIG9yIENvbWJuTnVtLg0KICAiLSIsIA0KICAiJXMiLCAjIFJ1biBTZWVkLg0KICAiLlJEUyINCikNCmBgYA0KDQojIyAyMDIxLTA0IERhdGENCmBgYHtyIHBhcmFtc30NCiMgQ29waWVkIGZyb20gTGF3TW9ydG9uMTk5Ni1OdW1lcmljYWxQb29sQ29tbXVuaXR5U2NhbGluZy1DYWxjdWxhdGlvbi5SDQpzZXQuc2VlZCgzODQyNzA0MikNCg0KYmFzYWwgPC0gYygzLCAxMCwgMzAsIDEwMCwgMzAwLCAxMDAwKQ0KY29uc3VtZXIgPC0gYygzLCAxMCwgMzAsIDEwMCwgMzAwLCAxMDAwKSAqIDINCmV2ZW50cyA8LSAobWF4KGJhc2FsKSArIG1heChjb25zdW1lcikpICogMg0KcnVucyA8LSAxMDANCg0KbG9nQm9keVNpemUgPC0gYygtMiwgLTEsIC0xLCAxKSAjIE1vcnRvbiBhbmQgTGF3IDE5OTcgdmVyc2lvbi4NCnBhcmFtZXRlcnMgPC0gYygwLjAxLCAxMCwgMC41LCAwLjIsIDEwMCwgMC4xKQ0KDQojIE5lZWQgdG8gcmVydW4gc2VlZHNQcmVwIHRvIGdldCB0aGUgcmFuZG9tIG51bWJlciBnZW5lcmF0aW9uIHJpZ2h0IGZvciBzZWVkc1J1bg0Kc2VlZHNQcmVwIDwtIHJ1bmlmKDIgKiBsZW5ndGgoYmFzYWwpICogbGVuZ3RoKGNvbnN1bWVyKSkgKiAxRTgNCnNlZWRzUnVuIDwtIHJ1bmlmKHJ1bnMgKiBsZW5ndGgoYmFzYWwpICogbGVuZ3RoKGNvbnN1bWVyKSkgKiAxRTgNCmBgYA0KDQpgYGB7ciBvcmdhbmlzZVBhcmFtc30NCnBhcmFtRnJhbWUgPC0gd2l0aChsaXN0KA0KICBiID0gcmVwKGJhc2FsLCB0aW1lcyA9IGxlbmd0aChjb25zdW1lcikpLA0KICBjID0gcmVwKGNvbnN1bWVyLCBlYWNoID0gbGVuZ3RoKGJhc2FsKSksDQogIHMxID0gc2VlZHNQcmVwWzE6KGxlbmd0aChiYXNhbCkgKiBsZW5ndGgoY29uc3VtZXIpKV0sDQogIHMyID0gc2VlZHNQcmVwWw0KICAgIChsZW5ndGgoYmFzYWwpICogbGVuZ3RoKGNvbnN1bWVyKSArIDEpOigNCiAgICAgIDIgKiBsZW5ndGgoYmFzYWwpICogbGVuZ3RoKGNvbnN1bWVyKSkNCiAgXSwNCiAgc1IgPSBzZWVkc1J1bg0KKSwgew0KICB0ZW1wIDwtIGRhdGEuZnJhbWUoDQogICAgQ29tYm5OdW0gPSAwLA0KICAgIEJhc2FscyA9IGIsDQogICAgQ29uc3VtZXJzID0gYywNCiAgICBTZWVkUG9vbCA9IHMxLA0KICAgIFNlZWRNYXQgPSBzMiwNCiAgICBTZWVkUnVucyA9ICIiLA0KICAgIFNlZWRSdW5zTnVtID0gMCwNCiAgICBFbmRTdGF0ZXMgPSBJKHJlcChsaXN0KCIiKSwgbGVuZ3RoKGIpKSksDQogICAgRW5kU3RhdGVzTnVtID0gMCwNCiAgICBFbmRTdGF0ZVNpemVzID0gSShyZXAobGlzdCgiIiksIGxlbmd0aChiKSkpLA0KICAgIEVuZFN0YXRlU2l6ZXNOdW0gPSBOQSwNCiAgICBFbmRTdGF0ZUFzc2VtYmx5ID0gSShyZXAobGlzdCgiIiksIGxlbmd0aChiKSkpLA0KICAgIEVuZFN0YXRlQWJ1bmRhbmNlID0gSShyZXAobGlzdCgiIiksIGxlbmd0aChiKSkpLA0KICAgIERhdGFzZXQgPSAiMjAyMS0wNCIsDQogICAgRGF0YXNldElEID0gMSwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KICBmb3IgKGkgaW4gMTpucm93KHRlbXApKSB7DQogICAgc2VlZHMgPC0gc1JbKChpIC0gMSkgKiBydW5zICsgMSkgOiAoaSAqIHJ1bnMpXQ0KICAgIHRlbXAkU2VlZFJ1bnNbaV0gPC0gdG9TdHJpbmcoc2VlZHMpICMgQ1NWDQogICAgdGVtcCRTZWVkUnVuc051bVtpXSA8LSBsZW5ndGgoc2VlZHMpDQogIH0NCiAgdGVtcCRDb21ibk51bSA8LSAxOm5yb3codGVtcCkNCiAgdGVtcA0KfSkNCmBgYA0KDQpgYGB7ciBsb2FkUmVzdWx0c30NCiMgTm90ZTogbiArIDIgZW5kIHN0YXRlcy4gRmFpbHVyZSB0byBmaW5pc2gsIGZhaWx1cmUgdG8gb2J0YWluIHN0YXRlLCBhbmQgc3RhdGUuDQpmb3IgKGkgaW4gMTpucm93KHBhcmFtRnJhbWUpKSB7DQogIHJlc3VsdHNMaXN0IDwtIGxpc3QoDQogICAgIk5vIFJ1biIgPSAwLA0KICAgICJObyBTdGF0ZSIgPSAwDQogICkNCiAgcmVzdWx0c1NpemUgPC0gbGlzdCgNCiAgICAiMCIgPSAwDQogICkNCiAgcmVzdWx0c0Fzc2VtYmx5IDwtIGxpc3QoDQogICAgIk5vIFJ1biIgPSBkYXRhLmZyYW1lKCksDQogICAgIk5vIFN0YXRlIiA9IGRhdGEuZnJhbWUoKQ0KICApDQogIHNlZWRzIDwtIHVubGlzdChzdHJzcGxpdChwYXJhbUZyYW1lJFNlZWRSdW5zW2ldLCAnLCAnKSkNCiAgZm9yIChzZWVkIGluIHNlZWRzKSB7DQogICAgZmlsZU5hbWUgPC0gZmlsZS5wYXRoKA0KICAgICAgZGlyVmlraW5nUmVzdWx0c1twYXJhbUZyYW1lJERhdGFzZXRJRFtpXV0sDQogICAgICBzcHJpbnRmKHJlc3VsdEZvcm1hdCwgcGFyYW1GcmFtZSRDb21ibk51bVtpXSwgc2VlZCkNCiAgICApDQogICAgDQogICAgaWYgKGZpbGUuZXhpc3RzKGZpbGVOYW1lKSkgew0KICAgICAgdGVtcCA8LSBsb2FkKGZpbGVOYW1lKQ0KICAgICAgdGVtcCA8LSBldmFsKHBhcnNlKHRleHQgPSB0ZW1wKSkgIyBHZXQgb2JqZWN0cy4NCiAgICAgIA0KICAgICAgaWYgKGlzLmRhdGEuZnJhbWUodGVtcCkpIHsNCiAgICAgICAgY29tbXVuaXR5IDwtIHRvU3RyaW5nKA0KICAgICAgICAgIHRlbXBbW25jb2wodGVtcCldXVtbbnJvdyh0ZW1wKV1dDQogICAgICAgICkNCiAgICAgICAgc2l6ZSA8LSB0b1N0cmluZyhsZW5ndGgodGVtcFtbbmNvbCh0ZW1wKV1dW1tucm93KHRlbXApXV0pKQ0KICAgICAgICANCiAgICAgICAgaWYgKGNvbW11bml0eSA9PSAiIikgew0KICAgICAgICAgIHJlc3VsdHNMaXN0JGBObyBTdGF0ZWAgPC0gcmVzdWx0c0xpc3QkYE5vIFN0YXRlYCArIDENCiAgICAgICAgICByZXN1bHRzU2l6ZSRgMGAgPC0gcmVzdWx0c1NpemUkYDBgICsgMQ0KICAgICAgICAgIA0KICAgICAgICB9IGVsc2UgaWYgKGNvbW11bml0eSAlaW4lIG5hbWVzKHJlc3VsdHNMaXN0KSkgew0KICAgICAgICAgIHJlc3VsdHNMaXN0W1tjb21tdW5pdHldXSA8LSByZXN1bHRzTGlzdFtbY29tbXVuaXR5XV0gKyAxDQogICAgICAgICAgcmVzdWx0c1NpemVbW3NpemVdXSA8LSByZXN1bHRzU2l6ZVtbc2l6ZV1dICsgMQ0KICAgICAgICAgIA0KICAgICAgICB9IGVsc2Ugew0KICAgICAgICAgIHJlc3VsdHNMaXN0W1tjb21tdW5pdHldXSA8LSAxDQogICAgICAgICAgcmVzdWx0c0Fzc2VtYmx5W1tjb21tdW5pdHldXSA8LSB0ZW1wDQogICAgICAgICAgDQogICAgICAgICAgaWYgKHNpemUgJWluJSByZXN1bHRzU2l6ZSkgew0KICAgICAgICAgICAgcmVzdWx0c1NpemVbW3NpemVdXSA8LSByZXN1bHRzU2l6ZVtbc2l6ZV1dICsgMQ0KICAgICAgICAgIH0gZWxzZSB7DQogICAgICAgICAgICByZXN1bHRzU2l6ZVtbc2l6ZV1dIDwtIDENCiAgICAgICAgICB9DQogICAgICAgIH0NCiAgICAgIH0gZWxzZSB7DQogICAgICAgIHJlc3VsdHNMaXN0JGBObyBTdGF0ZWAgPC0gcmVzdWx0c0xpc3QkYE5vIFN0YXRlYCArIDENCiAgICAgICAgcmVzdWx0c1NpemUkYDBgIDwtIHJlc3VsdHNTaXplJGAwYCArIDENCiAgICAgIH0NCiAgICB9IGVsc2Ugew0KICAgICAgcmVzdWx0c0xpc3QkYE5vIFJ1bmAgPC0gcmVzdWx0c0xpc3QkYE5vIFJ1bmAgKyAxDQogICAgICByZXN1bHRzU2l6ZSRgMGAgPC0gcmVzdWx0c1NpemUkYDBgICsgMQ0KICAgIH0NCiAgfQ0KICANCiAgcGFyYW1GcmFtZSRFbmRTdGF0ZXNbW2ldXSA8LSByZXN1bHRzTGlzdA0KICBwYXJhbUZyYW1lJEVuZFN0YXRlc051bVtpXSA8LSBsZW5ndGgocmVzdWx0c0xpc3QpIC0gMiAjICEgTm8gU3RhdGUsIE5vIFJ1bg0KICBwYXJhbUZyYW1lJEVuZFN0YXRlU2l6ZXNbW2ldXSA8LSByZXN1bHRzU2l6ZQ0KICBwYXJhbUZyYW1lJEVuZFN0YXRlU2l6ZXNOdW1baV0gPC0gbGVuZ3RoKHJlc3VsdHNTaXplKSAtIDEgIyAhIDANCiAgcGFyYW1GcmFtZSRFbmRTdGF0ZUFzc2VtYmx5W1tpXV0gPC0gcmVzdWx0c0Fzc2VtYmx5DQp9DQpgYGANCg0KIyMgMjAyMS0wNSBEYXRhDQpgYGB7ciBvcmdhbmlzZVBhcmFtczJ9DQpzb3VyY2UoDQogIGZpbGUucGF0aChnZXR3ZCgpLCANCiAgICAgICAgICAgICJMYXdNb3J0b24xOTk2LU51bWVyaWNhbFBvb2xDb21tdW5pdHlTY2FsaW5nLVNldHRpbmdzMi5SIikNCikNCg0Kb2xkTnJvdyA8LSBucm93KHBhcmFtRnJhbWUpDQoNCnBhcmFtRnJhbWUgPC0gcmJpbmQocGFyYW1GcmFtZSwgd2l0aChsaXN0KA0KICBiID0gcmVwKGJhc2FsLCB0aW1lcyA9IGxlbmd0aChjb25zdW1lcikpLA0KICBjID0gcmVwKGNvbnN1bWVyLCBlYWNoID0gbGVuZ3RoKGJhc2FsKSksDQogIHMxID0gc2VlZHNQcmVwWzE6KGxlbmd0aChiYXNhbCkgKiBsZW5ndGgoY29uc3VtZXIpKV0sDQogIHMyID0gc2VlZHNQcmVwWw0KICAgIChsZW5ndGgoYmFzYWwpICogbGVuZ3RoKGNvbnN1bWVyKSArIDEpOigNCiAgICAgIDIgKiBsZW5ndGgoYmFzYWwpICogbGVuZ3RoKGNvbnN1bWVyKSkNCiAgXSwNCiAgc1IgPSBzZWVkc1J1bg0KKSwgew0KICB0ZW1wIDwtIGRhdGEuZnJhbWUoDQogICAgQ29tYm5OdW0gPSAwLA0KICAgIEJhc2FscyA9IGIsDQogICAgQ29uc3VtZXJzID0gYywNCiAgICBTZWVkUG9vbCA9IHMxLA0KICAgIFNlZWRNYXQgPSBzMiwNCiAgICBTZWVkUnVucyA9ICIiLA0KICAgIFNlZWRSdW5zTnVtID0gMCwNCiAgICBFbmRTdGF0ZXMgPSBJKHJlcChsaXN0KCIiKSwgbGVuZ3RoKGIpKSksDQogICAgRW5kU3RhdGVzTnVtID0gMCwNCiAgICBFbmRTdGF0ZVNpemVzID0gSShyZXAobGlzdCgiIiksIGxlbmd0aChiKSkpLA0KICAgIEVuZFN0YXRlU2l6ZXNOdW0gPSBOQSwNCiAgICBFbmRTdGF0ZUFzc2VtYmx5ID0gSShyZXAobGlzdCgiIiksIGxlbmd0aChiKSkpLA0KICAgIEVuZFN0YXRlQWJ1bmRhbmNlID0gSShyZXAobGlzdCgiIiksIGxlbmd0aChiKSkpLA0KICAgIERhdGFzZXQgPSAiMjAyMS0wNSIsDQogICAgRGF0YXNldElEID0gMiwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KICBmb3IgKGkgaW4gMTpucm93KHRlbXApKSB7DQogICAgc2VlZHMgPC0gc1JbKChpIC0gMSkgKiBydW5zICsgMSkgOiAoaSAqIHJ1bnMpXQ0KICAgIHRlbXAkU2VlZFJ1bnNbaV0gPC0gdG9TdHJpbmcoc2VlZHMpICMgQ1NWDQogICAgdGVtcCRTZWVkUnVuc051bVtpXSA8LSBsZW5ndGgoc2VlZHMpDQogIH0NCiAgdGVtcCRDb21ibk51bSA8LSAxOm5yb3codGVtcCkNCiAgdGVtcA0KfSkNCikNCmBgYA0KDQpgYGB7ciBsb2FkUmVzdWx0czJ9DQojIE5vdGU6IG4gKyAyIGVuZCBzdGF0ZXMuIEZhaWx1cmUgdG8gZmluaXNoLCBmYWlsdXJlIHRvIG9idGFpbiBzdGF0ZSwgYW5kIHN0YXRlLg0KIyBNb2RpZmllZCBmcm9tIGFib3ZlLCBidXQgd2l0aCB0aGUgYWJ1bmRhbmNlIHJlY29yZGVkLg0KZm9yIChpIGluIChvbGROcm93ICsgMSk6bnJvdyhwYXJhbUZyYW1lKSkgew0KICByZXN1bHRzTGlzdCA8LSBsaXN0KA0KICAgICJObyBSdW4iID0gMCwNCiAgICAiTm8gU3RhdGUiID0gMA0KICApDQogIHJlc3VsdHNTaXplIDwtIGxpc3QoDQogICAgIjAiID0gMA0KICApDQogIHJlc3VsdHNBc3NlbWJseSA8LSBsaXN0KA0KICAgICJObyBSdW4iID0gZGF0YS5mcmFtZSgpLA0KICAgICJObyBTdGF0ZSIgPSBkYXRhLmZyYW1lKCkNCiAgKQ0KICByZXN1bHRzQWJ1bmQgPC0gbGlzdCgNCiAgICAiTm8gUnVuIiA9ICIiLA0KICAgICJObyBTdGF0ZSIgPSAiIg0KICApDQogIHNlZWRzIDwtIHVubGlzdChzdHJzcGxpdChwYXJhbUZyYW1lJFNlZWRSdW5zW2ldLCAnLCAnKSkNCiAgZm9yIChzZWVkIGluIHNlZWRzKSB7DQogICAgZmlsZU5hbWUgPC0gZmlsZS5wYXRoKA0KICAgICAgZGlyVmlraW5nUmVzdWx0c1twYXJhbUZyYW1lJERhdGFzZXRJRFtpXV0sDQogICAgICBzcHJpbnRmKHJlc3VsdEZvcm1hdCwgcGFyYW1GcmFtZSRDb21ibk51bVtpXSwgc2VlZCkNCiAgICApDQogICAgDQogICAgaWYgKGZpbGUuZXhpc3RzKGZpbGVOYW1lKSkgew0KICAgICAgdGVtcCA8LSBsb2FkKGZpbGVOYW1lKQ0KICAgICAgdGVtcCA8LSBldmFsKHBhcnNlKHRleHQgPSB0ZW1wKSkgIyBHZXQgb2JqZWN0cy4NCiAgICAgIA0KICAgICAgaWYgKGlzLmxpc3QodGVtcCkgJiYgIlJlc3VsdCIgJWluJSBuYW1lcyh0ZW1wKSkgew0KICAgICAgICANCiAgICAgICAgaWYgKGlzLmRhdGEuZnJhbWUodGVtcCRSZXN1bHQpKQ0KICAgICAgICAgIGNvbW11bml0eSA8LSB0ZW1wJFJlc3VsdCRDb21tdW5pdHlbW25yb3codGVtcCRSZXN1bHQpXV0NCiAgICAgICAgZWxzZSANCiAgICAgICAgICBjb21tdW5pdHkgPC0gdGVtcCRSZXN1bHQNCiAgICAgICAgDQogICAgICAgIHNpemUgPC0gdG9TdHJpbmcobGVuZ3RoKGNvbW11bml0eSkpDQogICAgICAgIA0KICAgICAgICBpZiAoY29tbXVuaXR5WzFdICE9ICIiKSANCiAgICAgICAgICBhYnVuZCA8LSB0b1N0cmluZyh0ZW1wJEFidW5kW2NvbW11bml0eSArIDFdKQ0KICAgICAgICBlbHNlIA0KICAgICAgICAgIGFidW5kIDwtICIiDQogICAgICAgIA0KICAgICAgICBjb21tdW5pdHkgPC0gdG9TdHJpbmcoY29tbXVuaXR5KQ0KICAgICAgICANCiAgICAgICAgaWYgKGNvbW11bml0eSA9PSAiIikgew0KICAgICAgICAgIHJlc3VsdHNMaXN0JGBObyBTdGF0ZWAgPC0gcmVzdWx0c0xpc3QkYE5vIFN0YXRlYCArIDENCiAgICAgICAgICByZXN1bHRzU2l6ZSRgMGAgPC0gcmVzdWx0c1NpemUkYDBgICsgMQ0KICAgICAgICAgIA0KICAgICAgICB9IGVsc2UgaWYgKGNvbW11bml0eSAlaW4lIG5hbWVzKHJlc3VsdHNMaXN0KSkgew0KICAgICAgICAgIHJlc3VsdHNMaXN0W1tjb21tdW5pdHldXSA8LSByZXN1bHRzTGlzdFtbY29tbXVuaXR5XV0gKyAxDQogICAgICAgICAgcmVzdWx0c1NpemVbW3NpemVdXSA8LSByZXN1bHRzU2l6ZVtbc2l6ZV1dICsgMQ0KICAgICAgICAgIA0KICAgICAgICB9IGVsc2Ugew0KICAgICAgICAgIHJlc3VsdHNMaXN0W1tjb21tdW5pdHldXSA8LSAxDQogICAgICAgICAgcmVzdWx0c0Fzc2VtYmx5W1tjb21tdW5pdHldXSA8LSB0ZW1wDQogICAgICAgICAgcmVzdWx0c0FidW5kW1tjb21tdW5pdHldXSA8LSBhYnVuZA0KICAgICAgICAgIA0KICAgICAgICAgIGlmIChzaXplICVpbiUgcmVzdWx0c1NpemUpIHsNCiAgICAgICAgICAgIHJlc3VsdHNTaXplW1tzaXplXV0gPC0gcmVzdWx0c1NpemVbW3NpemVdXSArIDENCiAgICAgICAgICB9IGVsc2Ugew0KICAgICAgICAgICAgcmVzdWx0c1NpemVbW3NpemVdXSA8LSAxDQogICAgICAgICAgfQ0KICAgICAgICB9DQogICAgICB9IGVsc2Ugew0KICAgICAgICByZXN1bHRzTGlzdCRgTm8gU3RhdGVgIDwtIHJlc3VsdHNMaXN0JGBObyBTdGF0ZWAgKyAxDQogICAgICAgIHJlc3VsdHNTaXplJGAwYCA8LSByZXN1bHRzU2l6ZSRgMGAgKyAxDQogICAgICB9DQogICAgfSBlbHNlIHsNCiAgICAgIHJlc3VsdHNMaXN0JGBObyBSdW5gIDwtIHJlc3VsdHNMaXN0JGBObyBSdW5gICsgMQ0KICAgICAgcmVzdWx0c1NpemUkYDBgIDwtIHJlc3VsdHNTaXplJGAwYCArIDENCiAgICB9DQogIH0NCiAgDQogIHBhcmFtRnJhbWUkRW5kU3RhdGVzW1tpXV0gPC0gcmVzdWx0c0xpc3QNCiAgcGFyYW1GcmFtZSRFbmRTdGF0ZXNOdW1baV0gPC0gbGVuZ3RoKHJlc3VsdHNMaXN0KSAtIDIgIyAhIE5vIFN0YXRlLCBObyBSdW4NCiAgcGFyYW1GcmFtZSRFbmRTdGF0ZVNpemVzW1tpXV0gPC0gcmVzdWx0c1NpemUNCiAgcGFyYW1GcmFtZSRFbmRTdGF0ZVNpemVzTnVtW2ldIDwtIGxlbmd0aChyZXN1bHRzU2l6ZSkgLSAxICMgISAwDQogIHBhcmFtRnJhbWUkRW5kU3RhdGVBc3NlbWJseVtbaV1dIDwtIHJlc3VsdHNBc3NlbWJseQ0KICBwYXJhbUZyYW1lJEVuZFN0YXRlQWJ1bmRhbmNlW1tpXV0gPC0gcmVzdWx0c0FidW5kDQp9DQpgYGANCg0KIyMgVGVzdCBEYXRhDQoNCmBgYHtyIGFkZFRlc3R9DQp0ZXN0Um93TnVtcyA8LSBucm93KHBhcmFtRnJhbWUpDQp0ZXN0Um93c1RvQWRkIDwtIGMoMiwgNikgIyBNYWtlIHN1cmUgdG8gcHV0IGluIG51bWVyaWNhbCBvcmRlciENCg0KcGFyYW1GcmFtZSA8LSB3aXRoKA0KICBsaXN0KA0KICAgIGJhc2FsMiA9IGMoNSwgMTAsIDE1KSwNCiAgICBjb25zdW1lcjIgPSBjKDIwLCA0MCwgNjApLA0KICAgIGxvZ0JvZHlTaXplID0gYygtMiwgLTEsIC0xLCAwKSwNCiAgICBwYXJhbWV0ZXJzID0gYygwLjAxLCAxMCwgMC41LCAwLjIsIDEwMCwgMC4xKQ0KICApLA0KICB7DQogICAgc2V0LnNlZWQoMzY4MDE4MCkNCiAgICBzZWVkc1ByZXAyIDwtIHJ1bmlmKDIgKiBsZW5ndGgoYmFzYWwyKSAqIGxlbmd0aChjb25zdW1lcjIpKSAqIDFFOA0KICAgIHdpdGgobGlzdCgNCiAgICAgIGIgPSByZXAoYmFzYWwyLCB0aW1lcyA9IGxlbmd0aChjb25zdW1lcjIpKSwNCiAgICAgIGMgPSByZXAoY29uc3VtZXIyLCBlYWNoID0gbGVuZ3RoKGJhc2FsMikpLA0KICAgICAgczEgPSBzZWVkc1ByZXAyWzE6KGxlbmd0aChiYXNhbDIpICogbGVuZ3RoKGNvbnN1bWVyMikpXSwNCiAgICAgIHMyID0gc2VlZHNQcmVwMlsNCiAgICAgICAgKGxlbmd0aChiYXNhbDIpICogbGVuZ3RoKGNvbnN1bWVyMikgKyAxKTooDQogICAgICAgICAgMiAqIGxlbmd0aChiYXNhbDIpICogbGVuZ3RoKGNvbnN1bWVyMikpDQogICAgICBdDQogICAgKSwgew0KICAgICAgcmJpbmQoDQogICAgICAgIHBhcmFtRnJhbWUsDQogICAgICAgIGRhdGEuZnJhbWUoDQogICAgICAgICAgQ29tYm5OdW0gPSB0ZXN0Um93c1RvQWRkLA0KICAgICAgICAgIEJhc2FscyA9IGJbdGVzdFJvd3NUb0FkZF0sDQogICAgICAgICAgQ29uc3VtZXJzID0gY1t0ZXN0Um93c1RvQWRkXSwNCiAgICAgICAgICBTZWVkUG9vbCA9IHMxW3Rlc3RSb3dzVG9BZGRdLA0KICAgICAgICAgIFNlZWRNYXQgPSBzMlt0ZXN0Um93c1RvQWRkXSwNCiAgICAgICAgICBTZWVkUnVucyA9ICIiLA0KICAgICAgICAgIFNlZWRSdW5zTnVtID0gMCwNCiAgICAgICAgICBFbmRTdGF0ZXMgPSBJKHJlcChsaXN0KCIiKSwgbGVuZ3RoKHRlc3RSb3dzVG9BZGQpKSksDQogICAgICAgICAgRW5kU3RhdGVzTnVtID0gMCwNCiAgICAgICAgICBFbmRTdGF0ZVNpemVzID0gSShyZXAobGlzdCgiIiksIGxlbmd0aCh0ZXN0Um93c1RvQWRkKSkpLA0KICAgICAgICAgIEVuZFN0YXRlU2l6ZXNOdW0gPSBOQSwNCiAgICAgICAgICBFbmRTdGF0ZUFzc2VtYmx5ID0gSShyZXAobGlzdCgiIiksIGxlbmd0aCh0ZXN0Um93c1RvQWRkKSkpLA0KICAgICAgICAgIEVuZFN0YXRlQWJ1bmRhbmNlID0gSShyZXAobGlzdCgiIiksIGxlbmd0aCh0ZXN0Um93c1RvQWRkKSkpLA0KICAgICAgICAgIERhdGFzZXQgPSAiVGVzdCIsDQogICAgICAgICAgRGF0YXNldElEID0gbWF4KHBhcmFtRnJhbWUkRGF0YXNldElEKSArIDEsDQogICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICAgICAgICkNCiAgICAgICkNCiAgICB9DQogICAgKQ0KICB9DQopDQoNCnRlc3RSb3dOdW1zIDwtICh0ZXN0Um93TnVtcyArIDEpOm5yb3cocGFyYW1GcmFtZSkNCnJlc3VsdHNMaXN0IDwtIGxpc3QoDQogIGxpc3QoDQogICAgIk5vIFJ1biIgPSAwLA0KICAgICJObyBTdGF0ZSIgPSAwLA0KICAgICIyLCA0LCA2LCAxMiwgMjkiID0gMSwNCiAgICAiMiwgNCwgNiwgMTMsIDI5IiA9IDENCiAgKSwNCiAgbGlzdCgNCiAgICAiTm8gUnVuIiA9IDAsDQogICAgIk5vIFN0YXRlIiA9IDAsDQogICAgIjgsIDEwLCAxMiwgMTQsIDE1LCAxNiwgMzksIDQzIiA9IDEsDQogICAgIjgsIDEyLCAxNCwgMTUsIDE2LCAzOCwgMzkiID0gMQ0KICApDQopDQpyZXN1bHRzU2l6ZSA8LSBsaXN0KA0KICBsaXN0KA0KICAgICIwIiA9IDAsDQogICAgIjUiID0gMg0KICApLA0KICBsaXN0KA0KICAgICIwIiA9IDAsDQogICAgIjgiID0gMSwNCiAgICAiNyIgPSAxDQogICkNCikNCnJlc3VsdHNBYnVuZCA8LSBsaXN0KA0KICBsaXN0KA0KICAgICJObyBSdW4iID0gIiIsDQogICAgIk5vIFN0YXRlIiA9ICIiLA0KICAgICIyLCA0LCA2LCAxMiwgMjkiID0gIjc0Mi44ODU1MzY3MTcxMiwgODAuNTc5MjMzMDcyNjI2LCAxNjIuMTI4Mzk5ODUwMjUzLCAyMC4yMDgyMTk4Njk5Mzg5LCAxOC44NTg5NDkwNTEwNDI5IiwNCiAgICAiMiwgNCwgNiwgMTMsIDI5IiA9ICI2NjguNjY0MTQzNTgxODM3LCAxMTkuMDI0MTQ2ODUxMDUyLCAxMjcuNjgwMjY5MzgzODY3LCAzMC42NTc5NjA4NjYwMzMsIDEzLjQ4NDQxOTQ3MDc5NDQiDQogICksDQogIGxpc3QoDQogICAgIk5vIFJ1biIgPSAiIiwNCiAgICAiTm8gU3RhdGUiID0gIiIsDQogICAgIjgsIDEwLCAxMiwgMTQsIDE1LCAxNiwgMzksIDQzIiA9ICIyMC43NjY1ODA3NjA2NDkxLCAzMi40NDYxMTY1MjYxNDU0LCA4MC40MDMzMzg3ODE4ODk1LCA4MTcuODc5NzIyMDMzMzU0LCAxMjEuMTM2NTcwNzgyODI4LCAxOC4wMzkwNjcxMDg4OTU3LCAxMi4zODM0NTYxMTc3MjcxLCAxOS45Njc0NzE4NTQzMTk2IiwNCiAgICAiOCwgMTIsIDE0LCAxNSwgMTYsIDM4LCAzOSIgPSAiODIuNTkyMDQ4NDkyODEyLCAxMzguMjY3Mzc5MTY2MDE0LCA5MzguMTU4NDM2Mzc5MTY2LCA1MS44NjEwOTYzNzQ1MDIxLCA1LjAzNTU2MjUxODM3NDkxLCAxNC4xMDE5MzQzMTQ1ODI1LCAyNS45MjMxMDYyNzExMjI4Ig0KICApDQopDQoNCmZvciAoaiBpbiBzZXFfYWxvbmcodGVzdFJvd051bXMpKSB7DQogIGkgPC0gdGVzdFJvd051bXNbal0NCiAgcGFyYW1GcmFtZSRFbmRTdGF0ZXNbW2ldXSA8LSByZXN1bHRzTGlzdFtbal1dDQogIHBhcmFtRnJhbWUkRW5kU3RhdGVzTnVtW2ldIDwtIGxlbmd0aChyZXN1bHRzTGlzdFtbal1dKSAtIDIgIyAhIE5vIFN0YXRlLCBObyBSdW4NCiAgcGFyYW1GcmFtZSRFbmRTdGF0ZVNpemVzW1tpXV0gPC0gcmVzdWx0c1NpemVbW2pdXQ0KICBwYXJhbUZyYW1lJEVuZFN0YXRlU2l6ZXNOdW1baV0gPC0gbGVuZ3RoKHJlc3VsdHNTaXplW1tqXV0pIC0gMSAjICEgMA0KICBwYXJhbUZyYW1lJEVuZFN0YXRlQWJ1bmRhbmNlW1tpXV0gPC0gcmVzdWx0c0FidW5kW1tqXV0NCn0NCg0KYGBgDQoNCiMjIFBsb3QNCg0KYGBge3IgcGxvdDNEfQ0KIyBYLCBZLCBCYXNhbCBhbmQgQ29uc3VtZXIuDQojIFogPSBTaXplcyBvZiB0aGUgRW5kc3RhdGVzLg0KDQpwbG90U2NhbGluZ0RhdGEgPC0gZGF0YS5mcmFtZSgNCiAgQ29tYm5OdW0gPSByZXAocGFyYW1GcmFtZSRDb21ibk51bSwgcGFyYW1GcmFtZSRFbmRTdGF0ZXNOdW0pLA0KICBCYXNhbHMgPSByZXAocGFyYW1GcmFtZSRCYXNhbHMsIHBhcmFtRnJhbWUkRW5kU3RhdGVzTnVtKSwNCiAgQ29uc3VtZXJzID0gcmVwKHBhcmFtRnJhbWUkQ29uc3VtZXJzLCBwYXJhbUZyYW1lJEVuZFN0YXRlc051bSksDQogIERhdGFzZXQgPSByZXAocGFyYW1GcmFtZSREYXRhc2V0LCBwYXJhbUZyYW1lJEVuZFN0YXRlc051bSksDQogIERhdGFzZXRJRCA9IHJlcChwYXJhbUZyYW1lJERhdGFzZXRJRCwgcGFyYW1GcmFtZSRFbmRTdGF0ZXNOdW0pDQopDQoNCiMgQ29tbXVuaXRpZXMNCmNvbW1zIDwtIHVubGlzdChsYXBwbHkocGFyYW1GcmFtZSRFbmRTdGF0ZXMsIG5hbWVzKSkNCmZyZXFzIDwtIHVubGlzdChwYXJhbUZyYW1lJEVuZFN0YXRlcykNCmFzbWJsIDwtIHVubGlzdChwYXJhbUZyYW1lJEVuZFN0YXRlQXNzZW1ibHksIHJlY3Vyc2l2ZSA9IEZBTFNFKQ0KYXNtYmwgPC0gYXNtYmxbY29tbXMgIT0gIk5vIFJ1biIgJiBjb21tcyAhPSAiTm8gU3RhdGUiXQ0KZnJlcXMgPC0gZnJlcXNbY29tbXMgIT0gIk5vIFJ1biIgJiBjb21tcyAhPSAiTm8gU3RhdGUiXQ0KY29tbXMgPC0gY29tbXNbY29tbXMgIT0gIk5vIFJ1biIgJiBjb21tcyAhPSAiTm8gU3RhdGUiXQ0KDQphc21ibCA8LSBsYXBwbHkoYXNtYmwsIGZ1bmN0aW9uKGQpIHsNCiAgaWYgKGlzLm51bGwoZCkpIHJldHVybihOQSkNCiAgaWYgKCJSZXN1bHQuT3V0Y29tZSIgJWluJSBuYW1lcyhkKSkNCiAgICBkICU+JSBkcGx5cjo6ZmlsdGVyKFJlc3VsdC5PdXRjb21lICE9ICJUeXBlIDEgKEZhaWx1cmUpIiAmIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBSZXN1bHQuT3V0Y29tZSAhPSAiUHJlc2VudCIpDQogIGVsc2UNCiAgICBkJFJlc3VsdCAlPiUgZHBseXI6OmZpbHRlcihPdXRjb21lICE9ICJUeXBlIDEgKEZhaWx1cmUpIiAmIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT3V0Y29tZSAhPSAiUHJlc2VudCIpDQp9KQ0KDQpwbG90U2NhbGluZ0RhdGEkQ29tbXVuaXRpZXMgPC0gY29tbXMNCnBsb3RTY2FsaW5nRGF0YSRDb21tdW5pdHlGcmVxIDwtIGZyZXFzDQpwbG90U2NhbGluZ0RhdGEkQ29tbXVuaXR5U2VxIDwtIGFzbWJsDQoNCiMgQ29tbXVuaXR5IFNpemUNCnRlbXAgPC0gdW5saXN0KGxhcHBseShzdHJzcGxpdChwbG90U2NhbGluZ0RhdGEkQ29tbXVuaXRpZXMsICcsJyksIGxlbmd0aCkpDQpwbG90U2NhbGluZ0RhdGEkQ29tbXVuaXR5U2l6ZSA8LSB0ZW1wDQoNCiMgRm9yIHVzYWdlIGJ5IHRoZSByZWFkZXIuDQoNCnBsb3RTY2FsaW5nIDwtIHBsb3RseTo6cGxvdF9seSgNCiAgcGxvdFNjYWxpbmdEYXRhLA0KICB4ID0gfkJhc2FscywNCiAgeSA9IH5Db25zdW1lcnMsDQogIHogPSB+Q29tbXVuaXR5U2l6ZSwNCiAgY29sb3IgPSB+RGF0YXNldCwNCiAgY29sb3JzID0gYygicmVkIiwgImJsdWUiLCAiYmxhY2siKQ0KKQ0KDQpwbG90U2NhbGluZyA8LSBwbG90bHk6OmFkZF9tYXJrZXJzKHBsb3RTY2FsaW5nKQ0KDQpwbG90U2NhbGluZyA8LSBwbG90bHk6OmxheW91dCgNCiAgcGxvdFNjYWxpbmcsDQogIHNjZW5lID0gbGlzdCgNCiAgICB4YXhpcyA9IGxpc3QodHlwZSA9ICJsb2ciKSwNCiAgICB5YXhpcyA9IGxpc3QodHlwZSA9ICJsb2ciKSwNCiAgICBjYW1lcmEgPSBsaXN0KA0KICAgICAgZXllID0gbGlzdCgNCiAgICAgICAgeCA9IC0xLjI1LCB5ID0gLTEuMjUsIHogPSAuMDUNCiAgICAgICkNCiAgICApDQogICkNCikNCg0KcGxvdFNjYWxpbmcNCmBgYA0KDQojIyBBYnVuZGFuY2VzDQoNCmBgYHtyIGxvYWRQb29sc01hdHN9DQojID4gcnVuaWYoMSkgKiAxRTgNCiMgWzFdIDgyNTk4Njc5DQpzZXQuc2VlZCg4MjU5ODY3OSkNCg0KbWF0cyA8LSBsaXN0KCkNCnBvb2xzYWxsIDwtIGxpc3QoKSAjIG5hbWUgcG9vbHMgdXNlZCBpbiBzYXZlIGRhdGE7IGJlIGNhcmVmdWwhDQoNCmZvciAoaSBpbiAxOmxlbmd0aChkaXJWaWtpbmcpKSB7DQogIHRlbXAgPC0gbG9hZChmaWxlLnBhdGgoDQogICAgZGlyVmlraW5nW2ldLCANCiAgICBwYXN0ZTAoIkxhd01vcnRvbjE5OTYtTnVtZXJpY2FsUG9vbENvbW11bml0eVNjYWxpbmctUG9vbE1hdHMiLCANCiAgICAgICAgICAgaWYgKGkgPiAxKSBpIGVsc2UgIiIsIA0KICAgICAgICAgICAiLlJEUyIpDQogICkpDQogIG1hdHNbW2ldXSA8LSBldmFsKHBhcnNlKHRleHQgPSB0ZW1wWzFdKSkNCiAgcG9vbHNhbGxbW2ldXSA8LSBldmFsKHBhcnNlKHRleHQgPSB0ZW1wWzJdKSkNCn0NCnBvb2xzIDwtIHBvb2xzYWxsDQoNCiMgQWRkIGluIHRoZSB0ZXN0IGRhdGFzZXRzLg0KcG9vbHNUZW1wIDwtIGxpc3QoKQ0KbWF0c1RlbXAgPC0gbGlzdCgpDQpmb3IgKHIgaW4gdGVzdFJvd051bXMpIHsNCiAgdGVzdFJvd1JvdyA8LSBwYXJhbUZyYW1lW3IsIF0NCiAgcG9vbHNUZW1wW1t0ZXN0Um93Um93JENvbWJuTnVtXV0gPC0gd2l0aCh0ZXN0Um93Um93LA0KICAgIFJNVFJDb2RlMjo6TGF3TW9ydG9uMTk5Nl9zcGVjaWVzKA0KICAgICAgQmFzYWwgPSBCYXNhbHMsDQogICAgICBDb25zdW1lciA9IENvbnN1bWVycywNCiAgICAgIFBhcmFtZXRlcnMgPSBjKDAuMDEsIDEwLCAwLjUsIDAuMiwgMTAwLCAwLjEpLA0KICAgICAgTG9nQm9keVNpemUgPSBjKC0yLCAtMSwgLTEsIDApLA0KICAgICAgc2VlZCA9IFNlZWRQb29sDQogICAgKQ0KICApDQogIG1hdHNUZW1wW1t0ZXN0Um93Um93JENvbWJuTnVtXV0gPC0gd2l0aCh0ZXN0Um93Um93LA0KICAgIFJNVFJDb2RlMjo6TGF3TW9ydG9uMTk5Nl9Db21tdW5pdHlNYXQoDQogICAgICBQb29sID0gcG9vbHNUZW1wW1tDb21ibk51bV1dLA0KICAgICAgUGFyYW1ldGVycyA9IGMoMC4wMSwgMTAsIDAuNSwgMC4yLCAxMDAsIDAuMSksDQogICAgICBzZWVkID0gU2VlZE1hdA0KICAgICkNCiAgKQ0KfQ0KcG9vbHNbW2kgKyAxXV0gPC0gcG9vbHNUZW1wDQptYXRzW1tpICsgMV1dIDwtIG1hdHNUZW1wDQoNCm9sZENhbmRpZGF0ZURhdGEgPC0gbG9hZChmaWxlLnBhdGgoZ2V0d2QoKSwgImNhbmRpZGF0ZURhdGFTb0Zhci5SZGF0YSIpKQ0Kb2xkQ2FuZGlkYXRlRGF0YSA8LSBldmFsKHBhcnNlKHRleHQgPSBvbGRDYW5kaWRhdGVEYXRhKSkNCmBgYA0KDQpgYGB7ciBjb21wdXRlQ2FuZGlkYXRlc30NCmNhbmRpZGF0ZURhdGEgPC0gcGxvdFNjYWxpbmdEYXRhICU+JSBkcGx5cjo6Z3JvdXBfYnkoDQogIENvbWJuTnVtLCBEYXRhc2V0DQopICU+JSBkcGx5cjo6bXV0YXRlKA0KICBPdGhlclN0ZWFkeVN0YXRlcyA9IGRwbHlyOjpuKCkgLSAxDQopICU+JSBkcGx5cjo6ZmlsdGVyKA0KICBPdGhlclN0ZWFkeVN0YXRlcyA+IDANCikNCmNhbmRpZGF0ZURhdGEgJT4lIGRwbHlyOjpzZWxlY3QoLUNvbW11bml0eVNlcSkNCmBgYA0KDQpgYGB7ciBsb2FkQWJ1bmRhbmNlc30NCiMgRmlyc3QsIGNoZWNrIGlmIGl0IGlzIGluIHRoZSBwYXJhbUZyYW1lLg0KIyBTZWNvbmQsIGNoZWNrIGlmIGl0IGlzIGluIHRoZSBzYXZlZCBkYXRhIGZyb20gdGhlIHByZXZpb3VzLg0KIyBPdGhlcndpc2UsIGlnbm9yZSBpdCwgd2UnbGwgZmlndXJlIG91dCB3aGF0IGl0IGlzIGFuZCB3aHkgaXQgaXMgbWlzc2luZyBsYXRlci4NCg0KY2FuZGlkYXRlRGF0YSRDb21tdW5pdHlBYnVuZCA8LSAiIg0KDQpmb3IgKHIgaW4gMTpucm93KGNhbmRpZGF0ZURhdGEpKSB7DQogICMgSUQgMTo0IGFyZSB1c2VkIHRvIGlkZW50aWZ5IHBhcmFtRnJhbWUsIDUgdXNlZCB0byBpZGVudGlmeSBhYnVuZGFuY2UNCiAgSUQgPC0gY2FuZGlkYXRlRGF0YVtyLCAxOjZdDQogIHBhcmFtRnJhbWVSb3cgPC0gcGFyYW1GcmFtZSAlPiUgZHBseXI6OmZpbHRlcigNCiAgICBDb21ibk51bSA9PSBJRCRDb21ibk51bSwNCiAgICBCYXNhbHMgPT0gSUQkQmFzYWxzLA0KICAgIENvbnN1bWVycyA9PSBJRCRDb25zdW1lcnMsDQogICAgRGF0YXNldCA9PSBJRCREYXRhc2V0DQogICkNCiAgDQogIGlmIChpcy5saXN0KHBhcmFtRnJhbWVSb3ckRW5kU3RhdGVBYnVuZGFuY2VbWzFdXSkpIHsNCiAgICBlbnRyeSA8LSB3aGljaChJRCRDb21tdW5pdGllcyA9PSBuYW1lcyhwYXJhbUZyYW1lUm93JEVuZFN0YXRlQWJ1bmRhbmNlW1sxXV0pKQ0KICAgIGlmIChsZW5ndGgoZW50cnkpKSB7DQogICAgICBjYW5kaWRhdGVEYXRhJENvbW11bml0eUFidW5kW3JdIDwtIHBhcmFtRnJhbWVSb3ckRW5kU3RhdGVBYnVuZGFuY2VbWzFdXVtbZW50cnldXQ0KICAgICAgbmV4dCgpDQogICAgfQ0KICB9DQogIA0KICBpZiAoSUQkRGF0YXNldCA9PSAiMjAyMS0wNCIpIHsNCiAgICANCiAgICBvbGRDYW5kRGF0Um93IDwtIG9sZENhbmRpZGF0ZURhdGEgJT4lIGRwbHlyOjpmaWx0ZXIoDQogICAgICBDb21ibk51bSA9PSBJRCRDb21ibk51bSwNCiAgICAgIEJhc2FscyA9PSBJRCRCYXNhbHMsDQogICAgICBDb25zdW1lcnMgPT0gSUQkQ29uc3VtZXJzLA0KICAgICAgQ29tbXVuaXRpZXMgPT0gSUQkQ29tbXVuaXRpZXMNCiAgICApDQogICAgDQogICAgaWYgKG5yb3cob2xkQ2FuZERhdFJvdykgPiAwKSB7DQogICAgICBpZiAob2xkQ2FuZERhdFJvdyRDb21tdW5pdHlBYnVuZCAhPSAiIikgew0KICAgICAgICBjYW5kaWRhdGVEYXRhJENvbW11bml0eUFidW5kW3JdIDwtIG9sZENhbmREYXRSb3ckQ29tbXVuaXR5QWJ1bmQNCiAgICAgIH0NCiAgICB9DQogIH0NCn0NCmBgYA0KDQpgYGB7ciBjcmVhdGVBYnVuZH0NCmZvciAociBpbiAxOm5yb3coY2FuZGlkYXRlRGF0YSkpIHsNCiAgaWYgKCEoY2FuZGlkYXRlRGF0YSRDb21tdW5pdHlBYnVuZFtyXSA9PSAiRmFpbHVyZSIgfA0KICAgICAgY2FuZGlkYXRlRGF0YSRDb21tdW5pdHlBYnVuZFtyXSA9PSAiIikpIG5leHQNCg0KICAjIFJhbmRvbSBndWVzc2VzLCBzdGFydGluZyBmcm9tIHN0cnVjdHVyZWQuDQogIHRlbXAgPC0gd2l0aCgNCiAgICBjYW5kaWRhdGVEYXRhW3IsIF0sDQogICAgUk1UUkNvZGUyOjpGaW5kU3RlYWR5U3RhdGVGcm9tRXN0aW1hdGUoDQogICAgICBQb29sID0gcG9vbHNbW0RhdGFzZXRJRF1dW1tDb21ibk51bV1dLA0KICAgICAgSW50ZXJhY3Rpb25NYXRyaXggPSBtYXRzW1tEYXRhc2V0SURdXVtbQ29tYm5OdW1dXSwNCiAgICAgIENvbW11bml0eSA9IENvbW11bml0aWVzLA0KICAgICAgUG9wdWxhdGlvbnMgPSBpZmVsc2UoDQogICAgICAgIHBvb2xzW1tEYXRhc2V0SURdXVtbQ29tYm5OdW1dXSRUeXBlWw0KICAgICAgICAgIFJNVFJDb2RlMjo6Q3N2Um93U3BsaXQoQ29tbXVuaXRpZXMpDQogICAgICAgIF0gPT0gIkJhc2FsIiwNCiAgICAgICAgMTAwMCwgMTApDQogICAgKQ0KICApDQoNCiAgaWYgKGFueSh0ZW1wKSA8IDFFLTQpIHsNCiAgICB0ZW1wIDwtICJFc3RpbWF0ZUZhaWx1cmUiDQogIH0gZWxzZSB7DQogICAgdGVtcCA8LSB0b1N0cmluZyh0ZW1wKQ0KICB9DQogIGNhbmRpZGF0ZURhdGEkQ29tbXVuaXR5QWJ1bmRbcl0gPC0gdGVtcA0KfQ0KYGBgDQoNCmBgYHtyIGZpbHRlck5vQWJ1bmR9DQpjYW5kaWRhdGVEYXRhIDwtIGNhbmRpZGF0ZURhdGEgJT4lIGRwbHlyOjpmaWx0ZXIoQ29tbXVuaXR5QWJ1bmQgIT0gIiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ29tbXVuaXR5QWJ1bmQgIT0gIkZhaWx1cmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENvbW11bml0eUFidW5kICE9ICJFc3RpbWF0ZUZhaWx1cmUiKQ0KYGBgDQoNCmBgYHtyIGNvbXB1dGVQcm9kdWN0aXZpdHl9DQpjYW5kaWRhdGVEYXRhJENvbW11bml0eVByb2QgPC0gTkENCmZvciAociBpbiAxOm5yb3coY2FuZGlkYXRlRGF0YSkpIHsNCiAgY2FuZGlkYXRlRGF0YSRDb21tdW5pdHlQcm9kW3JdIDwtIHdpdGgoDQogICAgY2FuZGlkYXRlRGF0YVtyLCBdLCANCiAgICBSTVRSQ29kZTI6OlByb2R1Y3Rpdml0eSgNCiAgICAgIFBvb2wgPSBwb29sc1tbRGF0YXNldElEXV1bW0NvbWJuTnVtXV0sIA0KICAgICAgSW50ZXJhY3Rpb25NYXRyaXggPSBtYXRzW1tEYXRhc2V0SURdXVtbQ29tYm5OdW1dXSwgDQogICAgICBDb21tdW5pdHkgPSBDb21tdW5pdGllcywgDQogICAgICBQb3B1bGF0aW9ucyA9IENvbW11bml0eUFidW5kDQogICAgKQ0KICApDQp9DQpgYGANCg0KIyMgU2ltcGxlIFBpcGUgUmVzdWx0cw0KYGBge3IgcGlwZUZVTn0NCmZ1bmN0aW9uQ29tbXVuaXR5IDwtIGZ1bmN0aW9uKA0KICAgIHRpbWUsIA0KICAgIENvbmNlbnRyYXRpb24sIA0KICAgIHBhcmFtZXRlcnMsIA0KICAgIEdyaWRUaGlja25lc3MsIyA9IEdyaWQsDQogICAgUG9vbCwjID0gcHJlcHJvY2Vzc2VkJHJlZERpc1Bvb2wsIA0KICAgIE1hdCwjID0gcHJlcHJvY2Vzc2VkJHJlZENvbU1hdCwgDQogICAgQXJlYSA9IDEsIA0KICAgIERpZmZ1c2lvbiA9IDEsIA0KICAgIFZlcmJvc2UgPSBGQUxTRSwNCiAgICBFeHRpbmN0aW9uVGhyZXNob2xkID0gMCwgDQogICAgQ2lyY3VsYXIgPSBGQUxTRQ0KKSB7DQogICAgaWYoVmVyYm9zZSkgcHJpbnQoQ29uY2VudHJhdGlvbikNCiAgICANCiAgICBDb25jZW50cmF0aW9uW0NvbmNlbnRyYXRpb24gPCBFeHRpbmN0aW9uVGhyZXNob2xkXSA8LSAwDQogICAgDQogICAgIyBDb2x1bW5zIGFyZSBwZXIgc3BlY2llcyBwaXBlcywNCiAgICAjIFJvd3MgYXJlIHRoZSBzYW1lIHBvaW50IG9uIGEgcGlwZSBidXQgZm9yIGVhY2ggc3BlY2llcy4NCiAgICBwaXBlTWF0cml4IDwtIG1hdHJpeChkYXRhID0gQ29uY2VudHJhdGlvbiwgbmNvbCA9IG5yb3coUG9vbCkpDQogICAgIyBOb3RlIHRoYXQgbWF0cml4IGF1dG9tYXRpY2FsbHkgZmlsbHMgZG93bndhcmQhDQogICAgDQogICAgIyBJZGVhIHdpdGggY2lyY3VsYXI6IGF0dGFjaCBkdXBsaWNhdGVzIG9mIGZpcnN0LCBsYXN0IGV2YWx1YXRpb24gcG9pbnRzLg0KICAgICMgVGhlbiBldmFsdWF0ZSBhcyBub3JtYWwuIEJlZm9yZSBmaW5pc2hpbmcsIHJlbW92ZSB0aGUgZHVwbGljYXRlIGVudHJpZXMuDQogICAgIyBPcmRlciBvZiBhZGRpdGlvbnMgbWF0dGVycy4NCiAgICBpZiAoQ2lyY3VsYXIgPiAwKSB7DQogICAgICBwaXBlTWF0cml4IDwtIHJiaW5kKHBpcGVNYXRyaXhbLUNpcmN1bGFyOi0xICsgMSArbnJvdyhwaXBlTWF0cml4KSwgXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgcGlwZU1hdHJpeCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgcGlwZU1hdHJpeFsxOkNpcmN1bGFyLCBdKQ0KICAgICAgDQogICAgICBpZiAoImR4IiAlaW4lIG5hbWVzKEdyaWRUaGlja25lc3MpKSB7DQogICAgICAgIEdyaWRUaGlja25lc3MgPC0gR3JpZFRoaWNrbmVzcyRkeA0KICAgICAgfQ0KICAgICAgR3JpZFRoaWNrbmVzcyA8LSBjKEdyaWRUaGlja25lc3NbLUNpcmN1bGFyOi0xICsgMSArIGxlbmd0aChHcmlkVGhpY2tuZXNzKV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgR3JpZFRoaWNrbmVzcywNCiAgICAgICAgICAgICAgICAgICAgICAgICBHcmlkVGhpY2tuZXNzWzE6Q2lyY3VsYXJdKQ0KICAgIH0NCiAgICANCiAgICBpZihWZXJib3NlKSBwcmludChwaXBlTWF0cml4KQ0KICAgIA0KICAgICMgRGlmZnVzaW9uDQogICAgcGlwZU1hdHJpeFRyYW4gPC0gYXBwbHkocGlwZU1hdHJpeCwgTUFSR0lOID0gMiwgRlVOID0gZnVuY3Rpb24oQywgQSwgRCwgRykgew0KICAgICAgUmVhY1RyYW46OnRyYW4uMUQoDQogICAgICAgIEMgPSBDLCBBID0gQSwgRCA9IEQsIGR4ID0gRw0KICAgICAgKSRkQw0KICAgIH0sIEEgPSBBcmVhLCBEID0gRGlmZnVzaW9uLCBHID0gR3JpZFRoaWNrbmVzcykNCiAgICANCiAgICBpZihWZXJib3NlKSBwcmludChwaXBlTWF0cml4VHJhbikNCiAgICANCiAgICAjIFBvaW50LXdpc2UgY29tbXVuaXR5IGR5bmFtaWNzDQogICAgcGlwZU1hdHJpeENvbW0gPC0gdChhcHBseSgNCiAgICAgIHBpcGVNYXRyaXgsIE1BUkdJTiA9IDEsIEZVTiA9IGZ1bmN0aW9uKEMsIHQsIFAsIE0pIHsNCiAgICAgIHVubGlzdChSTVRSQ29kZTI6OkdlbmVyYWxpc2VkTG90a2FWb2x0ZXJyYSh0ID0gdCwgeSA9IEMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJtcyA9IGxpc3QoDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYSA9IE0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgciA9IFAkUmVwcm9kdWN0aW9uUmF0ZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKSkNCiAgICB9LCB0ID0gdGltZSwgUCA9IFBvb2wsIE0gPSBNYXQpKQ0KICAgIA0KICAgIGlmKFZlcmJvc2UpIHByaW50KHBpcGVNYXRyaXhDb21tKQ0KICAgIA0KICAgICMgT3JkZXIgb2YgZGVsZXRpb25zIGRvZXMgbm90IG1hdHRlci4NCiAgICBpZihDaXJjdWxhcikgcGlwZU1hdHJpeFRyYW4gPC0gcGlwZU1hdHJpeFRyYW5bDQogICAgICBjKC0xOi1DaXJjdWxhciwgLTE6LUNpcmN1bGFyICsgMSArIG5yb3cocGlwZU1hdHJpeFRyYW4pKSwgXSANCiAgICBpZihDaXJjdWxhcikgcGlwZU1hdHJpeENvbW0gPC0gcGlwZU1hdHJpeENvbW1bDQogICAgICBjKC0xOi1DaXJjdWxhciwgLTE6LUNpcmN1bGFyICsgMSArIG5yb3cocGlwZU1hdHJpeENvbW0pKSwgXSANCiAgICANCiAgICBsaXN0KHBpcGVNYXRyaXhUcmFuICsgcGlwZU1hdHJpeENvbW0pDQogIH0NCg0KcGlwZUZVTiA8LSBmdW5jdGlvbihpLCBkYXQsIHBvb2wsIG1hdCwgDQogICAgICAgICAgICAgICAgICAgIEdyaWRMID0gMSwgR3JpZENlbGxzID0gMTAwLA0KICAgICAgICAgICAgICAgICAgICBUaW1lID0gc2VxKDAsIDUwMDAsIGJ5ID0gMSkpIHsNCiAgdGVtcCA8LSBkYXRbaSwgXQ0KICANCiAgcHJlcHJvY2Vzc2VkIDwtIFJNVFJDb2RlMjo6SXNsYW5kUHJlcHJvY2VzcygNCiAgICBQb29sID0gcG9vbCwgSW50ZXJhY3Rpb25NYXRyaXggPSBtYXQsDQogICAgQ29tbXVuaXRpZXMgPSBjKA0KICAgICAgbGlzdCh0ZW1wJENvbW11bml0aWVzWzFdKSwNCiAgICAgIGxpc3QodGVtcCRDb21tdW5pdGllc1syXSkNCiAgICApLA0KICAgIFBvcHVsYXRpb25zID0gYygNCiAgICAgIGxpc3QodGVtcCRDb21tdW5pdHlBYnVuZFsxXSksDQogICAgICBsaXN0KHRlbXAkQ29tbXVuaXR5QWJ1bmRbMl0pDQogICAgKSwNCiAgICBEaXNwZXJzYWxQb29sID0gMCwNCiAgICBEaXNwZXJzYWxJc2xhbmQgPSBtYXRyaXgoMCwgbnJvdyA9IDIsIG5jb2wgPSAyKSwNCiAgICBUb2xlcmFuY2UgPSAwLCANCiAgICBWZXJib3NlID0gRkFMU0UNCiAgKQ0KICANCiAgIyBTZXR1cCB0aGUgZGUgc3lzdGVtLg0KICBHcmlkIDwtIFJlYWNUcmFuOjpzZXR1cC5ncmlkLjFEKEwgPSBHcmlkTCwgTiA9IEdyaWRDZWxscykNCiAgDQogIGluaXRpYWxDb25kaXRpb24gPC0gd2l0aChwcmVwcm9jZXNzZWQsIHJiaW5kKA0KICAgICMgQ29tbXVuaXR5IDENCiAgICBtYXRyaXgoYWJ1bmRhbmNlX2luaXRbMTpsZW5ndGgocmVkQ29tKV0sIG5jb2wgPSBsZW5ndGgocmVkQ29tKSksDQogICAgIyBTcGFjZQ0KICAgIG1hdHJpeCgwLCBucm93ID0gR3JpZENlbGxzIC0gMiwgbmNvbCA9IGxlbmd0aChyZWRDb20pKSwNCiAgICAjIENvbW11bml0eSAyDQogICAgbWF0cml4KGFidW5kYW5jZV9pbml0W2xlbmd0aChyZWRDb20pICsgMTpsZW5ndGgocmVkQ29tKV0sIG5jb2wgPSBsZW5ndGgocmVkQ29tKSkNCiAgKSkNCiAgDQogIGRlU29sdmU6Om9kZS4xRCgNCiAgICB5ID0gaW5pdGlhbENvbmRpdGlvbiwgDQogICAgdGltZXMgPSBUaW1lLA0KICAgIGZ1bmMgPSBmdW5jdGlvbkNvbW11bml0eSwNCiAgICBwYXJtcyA9IHByZXByb2Nlc3NlZCRwYXJhbWV0ZXJzLA0KICAgIEQgPSAwLjAwMSwNCiAgICBuc3BlYyA9IGxlbmd0aChwcmVwcm9jZXNzZWQkcmVkQ29tKSwNCiAgICBWZXJib3NlID0gRkFMU0UsDQogICAgR3JpZFRoaWNrbmVzcyA9IEdyaWQsDQogICAgUG9vbCA9IHByZXByb2Nlc3NlZCRyZWRQb29sLCANCiAgICBNYXQgPSBwcmVwcm9jZXNzZWQkcmVkQ29tTWF0LCANCiAgKQ0KfQ0KYGBgDQoNCmBgYHtyIHBpcGV9DQojIEZvciBlYWNoIGdyb3VwLWRhdGFzZXQsDQojIEZvciBlYWNoIHBhaXIsDQojIFJ1biBQaXBlIER5bmFtaWNzLA0KIyBTYXZlIHRoZSByZXN1bHQgd2l0aCBpdHMgcGFpcmluZw0KY2FuZGlkYXRlRGF0YSRUb3RhbElEIDwtIHBhc3RlKGNhbmRpZGF0ZURhdGEkQ29tYm5OdW0sIGNhbmRpZGF0ZURhdGEkRGF0YXNldElEKQ0KDQpwaXBlSW50ZXJhY3Rpb25zIDwtIGxpc3QoKQ0KcGlwZUludGVyYWN0aW9uc0Z1bGwgPC0gbGlzdCgpDQoNCmZvciAoZ3JwIGluIHVuaXF1ZShjYW5kaWRhdGVEYXRhJFRvdGFsSUQpKSB7DQogIGNhbmRpZGF0ZURhdGFTdWJzZXQgPC0gY2FuZGlkYXRlRGF0YSAlPiUgZHBseXI6OmZpbHRlcihUb3RhbElEID09IGdycCkNCiAgDQogIGlmIChucm93KGNhbmRpZGF0ZURhdGFTdWJzZXQpID09IDEpIG5leHQoKQ0KICANCiAgcGFpcmluZ1Jlc3VsdHMgPC0gY29tYm4oDQogICAgbnJvdyhjYW5kaWRhdGVEYXRhU3Vic2V0KSwgMiwgDQogICAgcGlwZUZVTiwNCiAgICBkYXQgPSBjYW5kaWRhdGVEYXRhU3Vic2V0LCANCiAgICBwb29sID0gcG9vbHNbWw0KICAgICAgY2FuZGlkYXRlRGF0YVN1YnNldCREYXRhc2V0SURbMV0NCiAgICBdXVtbY2FuZGlkYXRlRGF0YVN1YnNldCRDb21ibk51bVsxXV1dLA0KICAgIG1hdCA9IG1hdHNbWw0KICAgICAgY2FuZGlkYXRlRGF0YVN1YnNldCREYXRhc2V0SURbMV0NCiAgICBdXVtbY2FuZGlkYXRlRGF0YVN1YnNldCRDb21ibk51bVsxXV1dLA0KICAgIHNpbXBsaWZ5ID0gRkFMU0UNCiAgKQ0KICANCiAgcGFpcmluZ1Jlc3VsdHNBYnVuZHMgPC0gbGFwcGx5KA0KICAgIHBhaXJpbmdSZXN1bHRzLCBmdW5jdGlvbihtYXQsIGNlbGxzID0gMTAwKSB7DQogICAgICAjIEZpbmFsIG91dGNvbWVzLCB3aXRob3V0IHRpbWUuDQogICAgICBtYXQgPC0gbWF0W25yb3cobWF0KSwgLTFdDQogICAgICByZXRWYWwgPC0gbGlzdCgpDQogICAgICBzcGVjaWVzIDwtIGxlbmd0aChtYXQpIC8gY2VsbHMNCiAgICAgICMgVGhlIHJlc3VsdHMgYXJlIGNvcGllcyBvZiB0aGUgcGlwZSBieSBzcGVjaWVzDQogICAgICAjIFdlIHdhbnQgdGhlIGVuZCBwb2ludHMuDQogICAgICBmb3IgKGkgaW4gYygxLCBjZWxscykpIHsNCiAgICAgICAgcmV0VmFsW1t0b1N0cmluZyhpKV1dIDwtIG1hdFtpICsgY2VsbHMgKiAoKDE6c3BlY2llcykgLSAxKV0NCiAgICAgIH0NCiAgICAgIHJldFZhbA0KICAgIH0NCiAgKQ0KICANCiAgcGlwZUludGVyYWN0aW9uc0Z1bGxbW2dycF1dIDwtIHBhaXJpbmdSZXN1bHRzDQogIHBpcGVJbnRlcmFjdGlvbnNbW2dycF1dIDwtIHBhaXJpbmdSZXN1bHRzQWJ1bmRzDQp9DQpgYGANCg0KYGBge3IgY29tcGFyZVBpcGVEeW5hbWljc30NCiMgRm9ybWF0IG9mIHRhYmxlIHNob3VsZCBiZToNCiMgSUQsIENvbW11bml0eSAxLCBDb21tdW5pdHkgMiwgT3V0Y29tZXMgMS0yLCBPdXRjb21lcyAxLTAtMg0KIyBGb3Igb3V0Y29tZXMsIHNwZWNpZXMgcHJlc2VuY2Ugd2lsbCBiZSB1c2VkLg0KDQpjb21tdW5pdGllcyA8LSBOVUxMDQp0b3RhbENvbW11bml0aWVzIDwtIE5VTEwNCmZvciAoZ3JwIGluIHVuaXF1ZShjYW5kaWRhdGVEYXRhJFRvdGFsSUQpKSB7DQogIGNhbmRpZGF0ZURhdGFTdWJzZXQgPC0gY2FuZGlkYXRlRGF0YSAlPiUgZHBseXI6OmZpbHRlcihUb3RhbElEID09IGdycCkNCiAgDQogIGlmIChucm93KGNhbmRpZGF0ZURhdGFTdWJzZXQpID4gMSkgew0KICAgIG5ld0NvbW11bml0aWVzIDwtIGNvbWJuKA0KICAgICAgY2FuZGlkYXRlRGF0YVN1YnNldCRDb21tdW5pdGllcywgMiwgDQogICAgKQ0KICAgIGNvbW11bml0aWVzIDwtIGMoY29tbXVuaXRpZXMsIG5ld0NvbW11bml0aWVzKQ0KICAgIHRvdGFsQ29tbXVuaXRpZXMgPC0gYyggIyBMYWJlbGxpbmcgaXMgd3JvbmcgaGVyZS4gTmVlZCB0QyBmcm9tIHBhaXJzIG9mIG5DLg0KICAgICAgdG90YWxDb21tdW5pdGllcywNCiAgICAgIGxpc3QoYXBwbHkobmV3Q29tbXVuaXRpZXMsIDIsIGZ1bmN0aW9uKGNvbXMpIHsNCiAgICAgICAgdG9TdHJpbmcoc29ydCh1bmlxdWUoUk1UUkNvZGUyOjpDc3ZSb3dTcGxpdChjb21zKSkpKQ0KICAgICAgfSkpDQogICAgKQ0KICB9DQp9DQoNCm1pblRocmVzaCA8LSBsYXBwbHkoY2FuZGlkYXRlRGF0YSRDb21tdW5pdHlBYnVuZCwgZnVuY3Rpb24oeCkgbWluKFJNVFJDb2RlMjo6Q3N2Um93U3BsaXQoeCkpICogMC4xKSAlPiUgdW5saXN0ICU+JSBtaW4NCg0KcGlwZUludGVyYWN0aW9uc1doaWNoIDwtIHVubGlzdChsYXBwbHkoDQogIHNlcV9hbG9uZyhwaXBlSW50ZXJhY3Rpb25zKSwgZnVuY3Rpb24oaSwgYnlJRCwgY29tbXVuaXRpZXMpIHsNCiAgICBsYXBwbHkoYnlJRFtbaV1dLCBmdW5jdGlvbihzeXN0ZW0sIGNvbW1zKSB7DQogICAgICBsYXBwbHkoc3lzdGVtLCBmdW5jdGlvbihwaXBlRW5kLCBjb21tcykgew0KICAgICAgICB0b1N0cmluZyhSTVRSQ29kZTI6OkNzdlJvd1NwbGl0KGNvbW1zKVsNCiAgICAgICAgICB3aGljaChwaXBlRW5kID4gbWluVGhyZXNoKQ0KICAgICAgICBdKQ0KICAgICAgfSwgY29tbXMgPSBjb21tcykNCiAgICB9LCBjb21tcyA9IGNvbW11bml0aWVzW1tpXV0pDQogIH0sIGJ5SUQgPSBwaXBlSW50ZXJhY3Rpb25zLCBjb21tdW5pdGllcyA9IHRvdGFsQ29tbXVuaXRpZXMNCikpDQoNCnBpcGVJbnRlcmFjdGlvblJlc3VsdHMgPC0gZGF0YS5mcmFtZSgNCiAgRGF0YXNldElEID0gcmVwKG5hbWVzKHBpcGVJbnRlcmFjdGlvbnMpLCANCiAgICAgICAgICAgICAgICAgIHVubGlzdChsYXBwbHkocGlwZUludGVyYWN0aW9ucywgbGVuZ3RoKSkpLA0KICBDb21tdW5pdHkxID0gY29tbXVuaXRpZXNbc2VxKGZyb20gPSAxLCB0byA9IGxlbmd0aChjb21tdW5pdGllcyksIGJ5ID0gMildLA0KICBDb21tdW5pdHkyID0gY29tbXVuaXRpZXNbc2VxKGZyb20gPSAyLCB0byA9IGxlbmd0aChjb21tdW5pdGllcyksIGJ5ID0gMildLA0KICBPdXRjb21lX1BpcGUxID0gcGlwZUludGVyYWN0aW9uc1doaWNoWw0KICAgIHNlcShmcm9tID0gMSwgdG8gPSBsZW5ndGgocGlwZUludGVyYWN0aW9uc1doaWNoKSwgYnkgPSAyKV0sDQogIE91dGNvbWVfUGlwZTIgPSBwaXBlSW50ZXJhY3Rpb25zV2hpY2hbDQogICAgc2VxKGZyb20gPSAxLCB0byA9IGxlbmd0aChwaXBlSW50ZXJhY3Rpb25zV2hpY2gpLCBieSA9IDIpXQ0KKQ0KDQpwaXBlSW50ZXJhY3Rpb25SZXN1bHRzDQpgYGANCg0KYGBge3IgbWF0Y2hlc30NCnBpcGVJbnRlcmFjdGlvblJlc3VsdHMgJT4lIGRwbHlyOjptdXRhdGUoDQogIEMxSW52YWRlZCA9IENvbW11bml0eTEgIT0gT3V0Y29tZV9QaXBlMSwNCiAgQzJJbnZhZGVkID0gQ29tbXVuaXR5MiAhPSBPdXRjb21lX1BpcGUyLA0KICBTdGFsZW1hdGUgPSAhQzFJbnZhZGVkICYgIUMySW52YWRlZCwNCiAgSHlicmlkID0gQzFJbnZhZGVkICYgQzJJbnZhZGVkLA0KKSAlPiUgZHBseXI6OnNlbGVjdCgtZHBseXI6OnN0YXJ0c193aXRoKCJPdXRjb21lIikpDQpgYGANCg0KIyBTYXZlIHdvcmtzcGFjZQ0KYGBge3Igc2F2ZX0NCnNhdmUoDQogIGNhbmRpZGF0ZURhdGEsDQogIHBpcGVJbnRlcmFjdGlvbnMsDQogIHBpcGVJbnRlcmFjdGlvbnNXaGljaCwNCiAgbWF0cywNCiAgcGFyYW1GcmFtZSwNCiAgcGxvdFNjYWxpbmdEYXRhLA0KICBwb29scywNCiAgZmlsZSA9ICJMTTE5OTYtTnVtUG9vbENvbS1RRGF0RGlmLTIwMjEtMDcuUkRhdGEiDQopDQpgYGANCg==